(WIP) support font-family style fallback with basic text layout

This commit is contained in:
nulano 2023-02-01 17:05:41 +00:00 committed by Nulano
parent 4721c31b19
commit 6002b0a49e
2 changed files with 714 additions and 27 deletions

View File

@ -720,6 +720,288 @@ class FreeTypeFont:
raise NotImplementedError(msg) from e
class FreeTypeFontFamily:
"""FreeType font family"""
# example:
# from PIL import Image, ImageDraw, ImageFont
# f1 = ImageFont.truetype("segoeui.ttf", 24)
# f2 = ImageFont.truetype("seguisym.ttf", 24)
# ff = ImageFont.FreeTypeFontFamily(f1, f2)
# s = "a↦ľ"
# im = Image.new("RGBA", (100, 100), "white")
# d = ImageDraw.Draw(im)
# d.text((10, 10), s, "black", f1)
# d.text((10, 40), s, "black", f2)
# d.text((10, 70), s, "black", ff)
# im.show()
def __init__(self, *fonts):
fonts_list = []
for font in fonts:
try:
fonts_list.append(
("", font.size, font.index, font.encoding, font.font_bytes)
)
except AttributeError:
fonts_list.append((font.path, font.size, font.index, font.encoding))
self.fonts = tuple(fonts_list)
self.font = core.getfamily(self.fonts, layout_engine=Layout.BASIC)
def getlength(self, text, mode="", direction=None, features=None, language=None):
"""
Returns length (in pixels with 1/64 precision) of given text when rendered
in font with provided direction, features, and language.
This is the amount by which following text should be offset.
Text bounding box may extend past the length in some fonts,
e.g. when using italics or accents.
The result is returned as a float; it is a whole number if using basic layout.
Note that the sum of two lengths may not equal the length of a concatenated
string due to kerning. If you need to adjust for kerning, include the following
character and subtract its length.
For example, instead of
.. code-block:: python
hello = font.getlength("Hello")
world = font.getlength("World")
hello_world = hello + world # not adjusted for kerning
assert hello_world == font.getlength("HelloWorld") # may fail
use
.. code-block:: python
hello = font.getlength("HelloW") - font.getlength("W") # adjusted for kerning
world = font.getlength("World")
hello_world = hello + world # adjusted for kerning
assert hello_world == font.getlength("HelloWorld") # True
or disable kerning with (requires libraqm)
.. code-block:: python
hello = draw.textlength("Hello", font, features=["-kern"])
world = draw.textlength("World", font, features=["-kern"])
hello_world = hello + world # kerning is disabled, no need to adjust
assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"])
.. versionadded:: 8.0.0
:param text: Text to measure.
:param mode: Used by some graphics drivers to indicate what mode the
driver prefers; if empty, the renderer may return either
mode. Note that the mode is always a string, to simplify
C-level implementations.
:param direction: Direction of the text. It can be 'rtl' (right to
left), 'ltr' (left to right) or 'ttb' (top to bottom).
Requires libraqm.
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
for example 'dlig' or 'ss01', but can be also
used to turn off default font features for
example '-liga' to disable ligatures or '-kern'
to disable kerning. To get all supported
features, see
https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
Requires libraqm.
:param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`_
Requires libraqm.
:return: Width for horizontal, height for vertical text.
"""
return self.font.getlength(text, mode, direction, features, language) / 64
def getbbox(
self,
text,
mode="",
direction=None,
features=None,
language=None,
stroke_width=0,
anchor=None,
):
"""
Returns bounding box (in pixels) of given text relative to given anchor
when rendered in font with provided direction, features, and language.
Use :py:meth:`getlength()` to get the offset of following text with
1/64 pixel precision. The bounding box includes extra margins for
some fonts, e.g. italics or accents.
.. versionadded:: 8.0.0
:param text: Text to render.
:param mode: Used by some graphics drivers to indicate what mode the
driver prefers; if empty, the renderer may return either
mode. Note that the mode is always a string, to simplify
C-level implementations.
:param direction: Direction of the text. It can be 'rtl' (right to
left), 'ltr' (left to right) or 'ttb' (top to bottom).
Requires libraqm.
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
for example 'dlig' or 'ss01', but can be also
used to turn off default font features for
example '-liga' to disable ligatures or '-kern'
to disable kerning. To get all supported
features, see
https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
Requires libraqm.
:param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`_
Requires libraqm.
:param stroke_width: The width of the text stroke.
:param anchor: The text anchor alignment. Determines the relative location of
the anchor to the text. The default alignment is top left.
See :ref:`text-anchors` for valid values.
:return: ``(left, top, right, bottom)`` bounding box
"""
_string_length_check(text)
size, offset = self.font.getsize(
text, mode, direction, features, language, anchor
)
left, top = offset[0] - stroke_width, offset[1] - stroke_width
width, height = size[0] + 2 * stroke_width, size[1] + 2 * stroke_width
return left, top, left + width, top + height
def getmask2(
self,
text,
mode="",
*,
direction=None,
features=None,
language=None,
stroke_width=0,
anchor=None,
ink=0,
start=None,
**kwargs,
):
"""
Create a bitmap for the text.
If the font uses antialiasing, the bitmap should have mode ``L`` and use a
maximum value of 255. If the font has embedded color data, the bitmap
should have mode ``RGBA``. Otherwise, it should have mode ``1``.
:param text: Text to render.
:param mode: Used by some graphics drivers to indicate what mode the
driver prefers; if empty, the renderer may return either
mode. Note that the mode is always a string, to simplify
C-level implementations.
.. versionadded:: 1.1.5
:param fill: Optional fill function. By default, an internal Pillow function
will be used.
Deprecated. This parameter will be removed in Pillow 10
(2023-07-01).
:param direction: Direction of the text. It can be 'rtl' (right to
left), 'ltr' (left to right) or 'ttb' (top to bottom).
Requires libraqm.
.. versionadded:: 4.2.0
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
for example 'dlig' or 'ss01', but can be also
used to turn off default font features for
example '-liga' to disable ligatures or '-kern'
to disable kerning. To get all supported
features, see
https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
Requires libraqm.
.. versionadded:: 4.2.0
:param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`_
Requires libraqm.
.. versionadded:: 6.0.0
:param stroke_width: The width of the text stroke.
.. versionadded:: 6.2.0
:param anchor: The text anchor alignment. Determines the relative location of
the anchor to the text. The default alignment is top left.
See :ref:`text-anchors` for valid values.
.. versionadded:: 8.0.0
:param ink: Foreground ink for rendering in RGBA mode.
.. versionadded:: 8.0.0
:param start: Tuple of horizontal and vertical offset, as text may render
differently when starting at fractional coordinates.
.. versionadded:: 9.4.0
:return: A tuple of an internal PIL storage memory instance as defined by the
:py:mod:`PIL.Image.core` interface module, and the text offset, the
gap between the starting coordinate and the first marking
"""
_string_length_check(text)
if start is None:
start = (0, 0)
def fill(width, height):
size = (width, height)
Image._decompression_bomb_check(size)
return Image.core.fill("RGBA" if mode == "RGBA" else "L", size)
return self.font.render(
text,
fill,
mode,
direction,
features,
language,
stroke_width,
anchor,
ink,
start[0],
start[1],
)
class TransposedFont:
"""Wrapper for writing rotated or mirrored text"""

View File

@ -67,6 +67,12 @@ static int have_raqm = 0;
#define LAYOUT_RAQM 1
typedef struct {
FT_Face face;
unsigned char *font_bytes;
} FontFamilyFont;
typedef struct {
FT_Face face;
int index, x_offset, x_advance, y_offset, y_advance;
unsigned int cluster;
} GlyphInfo;
@ -89,7 +95,14 @@ typedef struct {
int layout_engine;
} FontObject;
typedef struct {
PyObject_HEAD int font_count;
FontFamilyFont *fonts;
int layout_engine;
} FontFamilyObject;
static PyTypeObject Font_Type;
static PyTypeObject FontFamily_Type;
/* round a 26.6 pixel coordinate to the nearest integer */
#define PIXEL(x) ((((x) + 32) & -64) >> 6)
@ -238,6 +251,133 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
return (PyObject *)self;
}
static PyObject *
getfamily(PyObject *self_, PyObject *args, PyObject *kw) {
/* create a font family object from a list of file names and a sizes (in pixels) */
FontFamilyObject *self;
int error = 0;
PyTupleObject *fonts_tuple = NULL;
Py_ssize_t layout_engine = 0;
static char *kwlist[] = {"fonts", "layout_engine", NULL};
if (!library) {
PyErr_SetString(PyExc_OSError, "failed to initialize FreeType library");
return NULL;
}
if (!PyArg_ParseTupleAndKeywords(
args, kw, "O!|n", kwlist, &PyTuple_Type, &fonts_tuple, &layout_engine)) {
return NULL;
}
if (PyTuple_GET_SIZE(fonts_tuple) == 0) {
PyErr_BadArgument();
return NULL;
}
self = PyObject_New(FontFamilyObject, &FontFamily_Type);
if (!self) {
return NULL;
}
self->font_count = PyTuple_GET_SIZE(fonts_tuple);
self->layout_engine = layout_engine;
self->fonts = PyMem_New(FontFamilyFont, self->font_count);
if (!self->fonts) {
PyObject_Del(self);
return NULL;
}
FontFamilyFont *font = self->fonts;
for (int i = 0; i < self->font_count; ++i, ++font) {
char *filename;
Py_ssize_t size;
Py_ssize_t index;
unsigned char *encoding;
unsigned char *font_bytes = NULL;
Py_ssize_t font_bytes_size = 0;
if (!PyArg_ParseTuple(
PyTuple_GET_ITEM(fonts_tuple, i),
"etnns|y#",
Py_FileSystemDefaultEncoding, // TODO PyConfig.filesystem_encoding
&filename,
&size,
&index,
&encoding,
&font_bytes,
&font_bytes_size)) {
goto err;
}
font->face = NULL;
if (filename && font_bytes_size <= 0) {
font->font_bytes = NULL;
error = FT_New_Face(library, filename, index, &font->face);
} else {
/* need to have allocated storage for font_bytes for the life of the
* object.*/
/* Don't free this before FT_Done_Face */
font->font_bytes = PyMem_Malloc(font_bytes_size);
if (!font->font_bytes) {
error = FT_Err_Out_Of_Memory;
}
if (!error) {
memcpy(font->font_bytes, font_bytes, (size_t)font_bytes_size);
error = FT_New_Memory_Face(
library,
(FT_Byte *)font->font_bytes,
font_bytes_size,
index,
&font->face);
}
}
if (!error) {
error = FT_Set_Pixel_Sizes(font->face, 0, size);
}
if (!error && encoding && strlen((char *)encoding) == 4) {
FT_Encoding encoding_tag =
FT_MAKE_TAG(encoding[0], encoding[1], encoding[2], encoding[3]);
error = FT_Select_Charmap(font->face, encoding_tag);
}
if (filename) {
PyMem_Free(filename);
}
if (error) {
if (font->font_bytes) {
PyMem_Free(font->font_bytes);
font->font_bytes = NULL;
}
geterror(error);
goto err;
}
}
return (PyObject *)self;
err:
for (FontFamilyFont *f = self->fonts; f != font; ++f) {
if (f->font_bytes) {
PyMem_Free(f->font_bytes);
f->font_bytes = NULL;
}
if (f->face) {
FT_Done_Face(f->face);
f->face = NULL;
}
}
PyObject_Del(self);
return NULL;
}
#ifdef HAVE_RAQM
static size_t
@ -381,6 +521,7 @@ text_layout_raqm(
}
for (i = 0; i < count; i++) {
(*glyph_info)[i].face = self->face;
(*glyph_info)[i].index = glyphs[i].index;
(*glyph_info)[i].x_offset = glyphs[i].x_offset;
(*glyph_info)[i].x_advance = glyphs[i].x_advance;
@ -451,6 +592,7 @@ text_layout_fallback(
} else {
ch = PyUnicode_READ_CHAR(string, i);
}
(*glyph_info)[i].face = self->face;
(*glyph_info)[i].index = FT_Get_Char_Index(self->face, ch);
error = FT_Load_Glyph(self->face, (*glyph_info)[i].index, load_flags);
if (error) {
@ -483,6 +625,107 @@ text_layout_fallback(
return count;
}
static size_t
text_layout_family(
PyObject *string,
FontFamilyObject *self,
const char *dir,
PyObject *features,
const char *lang,
GlyphInfo **glyph_info,
int mask,
int color) {
int error, load_flags;
FT_ULong ch;
Py_ssize_t count;
FT_GlyphSlot glyph;
FT_UInt last_index = 0;
int i;
if (features != Py_None || dir != NULL || lang != NULL) {
PyErr_SetString(
PyExc_KeyError,
"setting text direction, language or font features is not supported "
"without libraqm");
}
if (!PyUnicode_Check(string)) {
PyErr_SetString(PyExc_TypeError, "expected string");
return 0;
}
count = 0;
if (PyUnicode_Check(string)) {
count = PyUnicode_GET_LENGTH(string);
} else {
PyBytes_AsStringAndSize(string, &buffer, &count);
}
if (count == 0) {
return 0;
}
(*glyph_info) = PyMem_New(GlyphInfo, count);
if ((*glyph_info) == NULL) {
PyErr_SetString(PyExc_MemoryError, "PyMem_New() failed");
return 0;
}
load_flags = FT_LOAD_DEFAULT;
if (mask) {
load_flags |= FT_LOAD_TARGET_MONO;
}
#ifdef FT_LOAD_COLOR
if (color) {
load_flags |= FT_LOAD_COLOR;
}
#endif
for (i = 0; i < count; i++) {
if (buffer) {
ch = buffer[i];
} else {
ch = PyUnicode_READ_CHAR(string, i);
}
FontFamilyFont *font = self->fonts;
int found = 0;
for (int j = 0; !found && j < self->font_count; j++, font++) {
(*glyph_info)[i].index = FT_Get_Char_Index(font->face, ch);
if ((*glyph_info)[i].index != 0) {
found = 1;
}
if (j == 0 || found) { /* use first font's missing glyph */
(*glyph_info)[i].face = font->face;
error = FT_Load_Glyph(font->face, (*glyph_info)[i].index, load_flags);
if (error) {
geterror(error);
return 0;
}
glyph = font->face->glyph;
(*glyph_info)[i].x_offset = 0;
(*glyph_info)[i].y_offset = 0;
if (FT_HAS_KERNING(font->face) && last_index &&
(*glyph_info)[i].index) {
FT_Vector delta;
if (FT_Get_Kerning(
font->face,
last_index,
(*glyph_info)[i].index,
ft_kerning_default,
&delta) == 0) {
(*glyph_info)[i - 1].x_advance += PIXEL(delta.x);
(*glyph_info)[i - 1].y_advance += PIXEL(delta.y);
}
}
(*glyph_info)[i].x_advance = glyph->metrics.horiAdvance;
// y_advance is only used in ttb, which is not supported by basic layout
(*glyph_info)[i].y_advance = 0;
last_index = (*glyph_info)[i].index;
(*glyph_info)[i].cluster = ch;
}
}
}
return count;
}
static size_t
text_layout(
PyObject *string,
@ -509,7 +752,7 @@ text_layout(
}
static PyObject *
font_getlength(FontObject *self, PyObject *args) {
text_getlength(void *self, int is_font_family, PyObject *args) {
int length; /* length along primary axis, in 26.6 precision */
GlyphInfo *glyph_info = NULL; /* computed text layout */
size_t i, count; /* glyph_info index and length */
@ -535,7 +778,20 @@ font_getlength(FontObject *self, PyObject *args) {
mask = mode && strcmp(mode, "1") == 0;
color = mode && strcmp(mode, "RGBA") == 0;
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
if (is_font_family) {
count = text_layout_family(
string,
(FontFamilyObject *)self,
dir,
features,
lang,
&glyph_info,
mask,
color);
} else {
count = text_layout(
string, (FontObject *)self, dir, features, lang, &glyph_info, mask, color);
}
if (PyErr_Occurred()) {
return NULL;
}
@ -557,9 +813,20 @@ font_getlength(FontObject *self, PyObject *args) {
return PyLong_FromLong(length);
}
static PyObject *
font_getlength(FontObject *self, PyObject *args) {
return text_getlength(self, 0, args);
}
static PyObject *
family_getlength(FontFamilyObject *self, PyObject *args) {
return text_getlength(self, 1, args);
}
static int
bounding_box_and_anchors(
FT_Face face,
void *self,
int is_font_family,
const char *anchor,
int horizontal_dir,
GlyphInfo *glyph_info,
@ -576,17 +843,30 @@ bounding_box_and_anchors(
int x_min, x_max, y_min, y_max; /* text bounding box, in pixels */
int x_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */
int error;
FT_Face face;
FT_Face primaryFace;
FT_Glyph glyph;
FT_BBox bbox; /* glyph bounding box */
size_t i; /* glyph_info index */
if (is_font_family) {
FontFamilyObject *family = (FontFamilyObject *)self;
primaryFace = family->fonts->face;
} else {
FontObject *font = (FontObject *)self;
primaryFace = font->face;
}
/*
* text bounds are given by:
* - bounding boxes of individual glyphs
* - pen line, i.e. 0 to `advanced` along primary axis
* this means point (0, 0) is part of the text bounding box
*/
face = NULL;
position = x_min = x_max = y_min = y_max = 0;
for (i = 0; i < count; i++) {
face = glyph_info[i].face;
if (horizontal_dir) {
px = PIXEL(position + glyph_info[i].x_offset);
py = PIXEL(glyph_info[i].y_offset);
@ -666,14 +946,14 @@ bounding_box_and_anchors(
}
switch (anchor[1]) {
case 'a': // ascender
y_anchor = PIXEL(face->size->metrics.ascender);
y_anchor = PIXEL(primaryFace->size->metrics.ascender);
break;
case 't': // top
y_anchor = y_max;
break;
case 'm': // middle (ascender + descender) / 2
y_anchor = PIXEL(
(face->size->metrics.ascender + face->size->metrics.descender) /
(primaryFace->size->metrics.ascender + primaryFace->size->metrics.descender) /
2
);
break;
@ -684,7 +964,7 @@ bounding_box_and_anchors(
y_anchor = y_min;
break;
case 'd': // descender
y_anchor = PIXEL(face->size->metrics.descender);
y_anchor = PIXEL(primaryFace->size->metrics.descender);
break;
default:
goto bad_anchor;
@ -736,7 +1016,7 @@ bad_anchor:
}
static PyObject *
font_getsize(FontObject *self, PyObject *args) {
text_getsize(void *self, int is_font_family, PyObject *args) {
int width, height, x_offset, y_offset;
int load_flags; /* FreeType load_flags parameter */
int error;
@ -765,7 +1045,15 @@ font_getsize(FontObject *self, PyObject *args) {
mask = mode && strcmp(mode, "1") == 0;
color = mode && strcmp(mode, "RGBA") == 0;
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
if (is_font_family) {
FontFamilyObject *family = (FontFamilyObject *)self;
count = text_layout_family(
string, family, dir, features, lang, &glyph_info, mask, color);
} else {
FontObject *font = (FontObject *) self;
count = text_layout(
string, font, dir, features, lang, &glyph_info, mask, color);
}
if (PyErr_Occurred()) {
return NULL;
}
@ -779,7 +1067,8 @@ font_getsize(FontObject *self, PyObject *args) {
}
error = bounding_box_and_anchors(
self->face,
self,
is_font_family,
anchor,
horizontal_dir,
glyph_info,
@ -802,12 +1091,23 @@ font_getsize(FontObject *self, PyObject *args) {
}
static PyObject *
font_render(FontObject *self, PyObject *args) {
font_getsize(FontObject *self, PyObject *args) {
return text_getsize(self, 0, args);
}
static PyObject *
family_getsize(FontFamilyObject *self, PyObject *args) {
return text_getsize(self, 1, args);
}
static PyObject *
text_render(void *self, int is_font_family, PyObject *args) {
int x, y; /* pen position, in 26.6 precision */
int px, py; /* position of current glyph, in pixels */
int x_min, y_max; /* text offset in 26.6 precision */
int load_flags; /* FreeType load_flags parameter */
int error;
FT_Face face;
FT_Glyph glyph;
FT_GlyphSlot glyph_slot;
FT_Bitmap bitmap;
@ -868,6 +1168,29 @@ font_render(FontObject *self, PyObject *args) {
foreground_ink = foreground_ink_long;
if (is_font_family) {
FontFamilyObject *family = (FontFamilyObject *)self;
#ifdef FT_COLOR_H
for (int i = 0; i < family->font_count; i++) {
if (color) {
FT_Color foreground_color;
FT_Byte *ink = (FT_Byte *)&foreground_ink;
foreground_color.red = ink[0];
foreground_color.green = ink[1];
foreground_color.blue = ink[2];
foreground_color.alpha =
(FT_Byte)255; /* ink alpha is handled in ImageDraw.text */
FT_Palette_Set_Foreground_Color(family->fonts[i].face, foreground_color);
}
}
#endif
count =
text_layout_family(string, family, dir, features, lang, &glyph_info, mask, color);
} else {
FontObject *font = (FontObject *)self;
#ifdef FT_COLOR_H
if (color) {
FT_Color foreground_color;
@ -877,11 +1200,13 @@ font_render(FontObject *self, PyObject *args) {
foreground_color.blue = ink[2];
foreground_color.alpha =
(FT_Byte)255; /* ink alpha is handled in ImageDraw.text */
FT_Palette_Set_Foreground_Color(self->face, foreground_color);
FT_Palette_Set_Foreground_Color(font->face, foreground_color);
}
#endif
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
count =
text_layout(string, font, dir, features, lang, &glyph_info, mask, color);
}
if (PyErr_Occurred()) {
return NULL;
}
@ -897,7 +1222,8 @@ font_render(FontObject *self, PyObject *args) {
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
error = bounding_box_and_anchors(
self->face,
self,
is_font_family,
anchor,
horizontal_dir,
glyph_info,
@ -960,14 +1286,15 @@ font_render(FontObject *self, PyObject *args) {
px = PIXEL(x + glyph_info[i].x_offset);
py = PIXEL(y + glyph_info[i].y_offset);
face = glyph_info[i].face;
error =
FT_Load_Glyph(self->face, glyph_info[i].index, load_flags | FT_LOAD_RENDER);
FT_Load_Glyph(face, glyph_info[i].index, load_flags | FT_LOAD_RENDER);
if (error) {
geterror(error);
goto glyph_error;
}
glyph_slot = self->face->glyph;
glyph_slot = face->glyph;
bitmap = glyph_slot->bitmap;
if (glyph_slot->bitmap_top + py > y_max) {
@ -993,13 +1320,14 @@ font_render(FontObject *self, PyObject *args) {
px = PIXEL(x + glyph_info[i].x_offset);
py = PIXEL(y + glyph_info[i].y_offset);
error = FT_Load_Glyph(self->face, glyph_info[i].index, load_flags);
face = glyph_info[i].face;
error = FT_Load_Glyph(face, glyph_info[i].index, load_flags);
if (error) {
geterror(error);
goto glyph_error;
}
glyph_slot = self->face->glyph;
glyph_slot = face->glyph;
if (stroker != NULL) {
error = FT_Get_Glyph(glyph_slot, &glyph);
if (!error) {
@ -1214,6 +1542,16 @@ glyph_error:
return NULL;
}
static PyObject *
font_render(FontObject *self, PyObject *args) {
return text_render(self, 0, args);
}
static PyObject *
family_render(FontFamilyObject *self, PyObject *args) {
return text_render(self, 1, args);
}
#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
static PyObject *
@ -1545,8 +1883,74 @@ static PyTypeObject Font_Type = {
font_getsetters, /*tp_getset*/
};
static void
family_dealloc(FontFamilyObject *self) {
FontFamilyFont *font = self->fonts;
for (int i = 0; i < self->font_count; ++i, ++font) {
if (font->face) {
FT_Done_Face(font->face);
}
if (font->font_bytes) {
PyMem_Free(font->font_bytes);
}
}
PyMem_Free(self->fonts);
PyObject_Del(self);
}
static PyMethodDef family_methods[] = {
{"render", (PyCFunction)family_render, METH_VARARGS},
{"getsize", (PyCFunction)family_getsize, METH_VARARGS},
{"getlength", (PyCFunction)family_getlength, METH_VARARGS},
/* TODO
#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
{"getvarnames", (PyCFunction)font_getvarnames, METH_NOARGS},
{"getvaraxes", (PyCFunction)font_getvaraxes, METH_NOARGS},
{"setvarname", (PyCFunction)font_setvarname, METH_VARARGS},
{"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS},
#endif
*/
{NULL, NULL}};
static PyTypeObject FontFamily_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "FontFamily",
sizeof(FontObject),
0,
/* methods */
(destructor)family_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number */
0, /*tp_as_sequence */
0, /*tp_as_mapping */
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
family_methods, /*tp_methods*/
0, /*tp_members*/
0, /*TODO tp_getset*/
};
static PyMethodDef _functions[] = {
{"getfont", (PyCFunction)getfont, METH_VARARGS | METH_KEYWORDS}, {NULL, NULL}
{"getfont", (PyCFunction)getfont, METH_VARARGS | METH_KEYWORDS},
{"getfamily", (PyCFunction)getfamily, METH_VARARGS | METH_KEYWORDS},
{NULL, NULL}
};
static int
@ -1559,6 +1963,7 @@ setup_module(PyObject *m) {
/* Ready object type */
PyType_Ready(&Font_Type);
PyType_Ready(&FontFamily_Type);
if (FT_Init_FreeType(&library)) {
return 0; /* leave it uninitialized */