mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-04 04:20:10 +03:00
Merge 9ccec98cb1
into a4c4b11a44
This commit is contained in:
commit
5d55759c17
|
@ -720,6 +720,402 @@ class FreeTypeFont:
|
||||||
raise NotImplementedError(msg) from e
|
raise NotImplementedError(msg) from e
|
||||||
|
|
||||||
|
|
||||||
|
class FreeTypeFontFamily:
|
||||||
|
"""FreeType font family"""
|
||||||
|
|
||||||
|
# example:
|
||||||
|
# from PIL import Image, ImageDraw, ImageFont
|
||||||
|
# le = ImageFont.Layout.RAQM
|
||||||
|
# f1 = ImageFont.truetype(
|
||||||
|
# r"C:\Users\Nulano\AppData\Local\Microsoft\Windows\Fonts\SCRIPTIN.ttf", 24)
|
||||||
|
# f2 = ImageFont.truetype("segoeui.ttf", 24)
|
||||||
|
# f3 = ImageFont.truetype("seguisym.ttf", 24)
|
||||||
|
# ff = ImageFont.FreeTypeFontFamily(f1, f2, f3, layout_engine=le)
|
||||||
|
# for s in ("testčingšsšccčcč", "ية↦α,abc", "a↦ľ", "ῶ,ω̃,ώ,ώ, ́,á"):
|
||||||
|
# im = Image.new("RGBA", (300, 300), "white")
|
||||||
|
# d = ImageDraw.Draw(im)
|
||||||
|
# d.text((10, 60), s, "black", f1, direction="ltr", anchor="ls")
|
||||||
|
# d.text((10, 160), s, "black", f2, direction="ltr", anchor="ls")
|
||||||
|
# d.text((10, 260), s, "black", ff, direction="ltr", anchor="ls")
|
||||||
|
# im.show()
|
||||||
|
|
||||||
|
def __init__(self, *fonts, layout_engine=None):
|
||||||
|
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)
|
||||||
|
|
||||||
|
if layout_engine not in (Layout.BASIC, Layout.RAQM):
|
||||||
|
layout_engine = Layout.BASIC
|
||||||
|
if core.HAVE_RAQM:
|
||||||
|
layout_engine = Layout.RAQM
|
||||||
|
elif layout_engine == Layout.RAQM and not core.HAVE_RAQM:
|
||||||
|
warnings.warn(
|
||||||
|
"Raqm layout was requested, but Raqm is not available. "
|
||||||
|
"Falling back to basic layout."
|
||||||
|
)
|
||||||
|
layout_engine = Layout.BASIC
|
||||||
|
self.layout_engine = layout_engine
|
||||||
|
|
||||||
|
self.font = core.getfamily(self.fonts, layout_engine=self.layout_engine)
|
||||||
|
|
||||||
|
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 getmask(
|
||||||
|
self,
|
||||||
|
text,
|
||||||
|
mode="",
|
||||||
|
direction=None,
|
||||||
|
features=None,
|
||||||
|
language=None,
|
||||||
|
stroke_width=0,
|
||||||
|
anchor=None,
|
||||||
|
ink=0,
|
||||||
|
start=None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
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 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,
|
||||||
|
specifically ``la`` for horizontal text and ``lt`` for
|
||||||
|
vertical text. See :ref:`text-anchors` for details.
|
||||||
|
|
||||||
|
.. 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: An internal PIL storage memory instance as defined by the
|
||||||
|
:py:mod:`PIL.Image.core` interface module.
|
||||||
|
"""
|
||||||
|
return self.getmask2(
|
||||||
|
text,
|
||||||
|
mode,
|
||||||
|
direction=direction,
|
||||||
|
features=features,
|
||||||
|
language=language,
|
||||||
|
stroke_width=stroke_width,
|
||||||
|
anchor=anchor,
|
||||||
|
ink=ink,
|
||||||
|
start=start,
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
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],
|
||||||
|
)
|
||||||
|
|
||||||
|
def getmetrics(self):
|
||||||
|
"""
|
||||||
|
:return: A tuple of the maximum font ascent (the distance from the baseline to
|
||||||
|
the highest outline point) and maximum descent (the distance from the
|
||||||
|
baseline to the lowest outline point, a negative value)
|
||||||
|
"""
|
||||||
|
return self.font.ascent, self.font.descent
|
||||||
|
|
||||||
|
|
||||||
class TransposedFont:
|
class TransposedFont:
|
||||||
"""Wrapper for writing rotated or mirrored text"""
|
"""Wrapper for writing rotated or mirrored text"""
|
||||||
|
|
||||||
|
|
714
src/_imagingft.c
714
src/_imagingft.c
|
@ -67,6 +67,13 @@ static int have_raqm = 0;
|
||||||
#define LAYOUT_RAQM 1
|
#define LAYOUT_RAQM 1
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
FT_Face *faces;
|
||||||
|
int font_count;
|
||||||
|
int layout_engine;
|
||||||
|
} FontFamily;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
FT_Face face;
|
||||||
int index, x_offset, x_advance, y_offset, y_advance;
|
int index, x_offset, x_advance, y_offset, y_advance;
|
||||||
unsigned int cluster;
|
unsigned int cluster;
|
||||||
} GlyphInfo;
|
} GlyphInfo;
|
||||||
|
@ -89,7 +96,13 @@ typedef struct {
|
||||||
int layout_engine;
|
int layout_engine;
|
||||||
} FontObject;
|
} FontObject;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject_HEAD FontFamily data;
|
||||||
|
unsigned char **font_bytes;
|
||||||
|
} FontFamilyObject;
|
||||||
|
|
||||||
static PyTypeObject Font_Type;
|
static PyTypeObject Font_Type;
|
||||||
|
static PyTypeObject FontFamily_Type;
|
||||||
|
|
||||||
/* round a 26.6 pixel coordinate to the nearest integer */
|
/* round a 26.6 pixel coordinate to the nearest integer */
|
||||||
#define PIXEL(x) ((((x) + 32) & -64) >> 6)
|
#define PIXEL(x) ((((x) + 32) & -64) >> 6)
|
||||||
|
@ -238,56 +251,220 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
|
||||||
return (PyObject *)self;
|
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) */
|
||||||
|
int i, j;
|
||||||
|
FontFamilyObject *self;
|
||||||
|
FontFamily *family;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
family = &self->data;
|
||||||
|
|
||||||
|
family->font_count = PyTuple_GET_SIZE(fonts_tuple);
|
||||||
|
family->layout_engine = layout_engine;
|
||||||
|
family->faces = PyMem_New(FT_Face, family->font_count);
|
||||||
|
if (!family->faces) {
|
||||||
|
PyObject_Del(self);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
self->font_bytes = PyMem_New(unsigned char *, family->font_count);
|
||||||
|
if (!self->font_bytes) {
|
||||||
|
PyMem_Free(family->faces);
|
||||||
|
PyObject_Del(self);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < family->font_count; i++) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
family->faces[i] = NULL;
|
||||||
|
|
||||||
|
if (filename && font_bytes_size <= 0) {
|
||||||
|
self->font_bytes[i] = NULL;
|
||||||
|
error = FT_New_Face(library, filename, index, &family->faces[i]);
|
||||||
|
} else {
|
||||||
|
/* need to have allocated storage for font_bytes for the life of the
|
||||||
|
* object.*/
|
||||||
|
/* Don't free this before FT_Done_Face */
|
||||||
|
self->font_bytes[i] = PyMem_Malloc(font_bytes_size);
|
||||||
|
if (!self->font_bytes[i]) {
|
||||||
|
error = FT_Err_Out_Of_Memory;
|
||||||
|
}
|
||||||
|
if (!error) {
|
||||||
|
memcpy(self->font_bytes[i], font_bytes, (size_t)font_bytes_size);
|
||||||
|
error = FT_New_Memory_Face(
|
||||||
|
library,
|
||||||
|
(FT_Byte *)self->font_bytes[i],
|
||||||
|
font_bytes_size,
|
||||||
|
index,
|
||||||
|
&family->faces[i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
error = FT_Set_Pixel_Sizes(family->faces[i], 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(family->faces[i], encoding_tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filename) {
|
||||||
|
PyMem_Free(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
if (self->font_bytes[i]) {
|
||||||
|
PyMem_Free(self->font_bytes[i]);
|
||||||
|
self->font_bytes[i] = NULL;
|
||||||
|
}
|
||||||
|
if (family->faces[i]) {
|
||||||
|
FT_Done_Face(family->faces[i]);
|
||||||
|
}
|
||||||
|
geterror(error);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (PyObject *)self;
|
||||||
|
|
||||||
|
err:
|
||||||
|
for (j = 0; j < i; j++) {
|
||||||
|
if (family->faces[j]) {
|
||||||
|
FT_Done_Face(family->faces[j]);
|
||||||
|
}
|
||||||
|
if (self->font_bytes[j]) {
|
||||||
|
PyMem_Free(self->font_bytes[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PyObject_Del(self);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FT_Pos
|
||||||
|
family_getascender(FontFamily *family) {
|
||||||
|
int i;
|
||||||
|
FT_Pos ascender = 0;
|
||||||
|
for (i = 0; i < family->font_count; i++) {
|
||||||
|
ascender = (ascender > family->faces[i]->size->metrics.ascender)
|
||||||
|
? ascender
|
||||||
|
: family->faces[i]->size->metrics.ascender;
|
||||||
|
}
|
||||||
|
return ascender;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FT_Pos
|
||||||
|
family_getdescender(FontFamily *family) {
|
||||||
|
int i;
|
||||||
|
FT_Pos descender = 0;
|
||||||
|
for (i = 0; i < family->font_count; i++) {
|
||||||
|
descender = (descender < family->faces[i]->size->metrics.descender)
|
||||||
|
? descender
|
||||||
|
: family->faces[i]->size->metrics.descender;
|
||||||
|
}
|
||||||
|
return descender;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FT_Pos
|
||||||
|
family_getheight(FontFamily *family) {
|
||||||
|
int i;
|
||||||
|
FT_Pos height = 0;
|
||||||
|
for (i = 0; i < family->font_count; i++) {
|
||||||
|
height = (height > family->faces[i]->size->metrics.height)
|
||||||
|
? height
|
||||||
|
: family->faces[i]->size->metrics.height;
|
||||||
|
}
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_RAQM
|
#ifdef HAVE_RAQM
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
text_layout_raqm(
|
text_layout_raqm(
|
||||||
PyObject *string,
|
PyObject *string,
|
||||||
FontObject *self,
|
FontFamily *family,
|
||||||
const char *dir,
|
const char *dir,
|
||||||
PyObject *features,
|
PyObject *features,
|
||||||
const char *lang,
|
const char *lang,
|
||||||
GlyphInfo **glyph_info
|
GlyphInfo **glyph_info
|
||||||
) {
|
) {
|
||||||
|
int face = 0;
|
||||||
size_t i = 0, count = 0, start = 0;
|
size_t i = 0, count = 0, start = 0;
|
||||||
raqm_t *rq;
|
raqm_t *rq = NULL;
|
||||||
raqm_glyph_t *glyphs = NULL;
|
raqm_glyph_t *glyphs = NULL;
|
||||||
raqm_direction_t direction;
|
raqm_direction_t direction;
|
||||||
|
|
||||||
rq = raqm_create();
|
char *buffer = NULL;
|
||||||
if (rq == NULL) {
|
Py_UCS4 *text = NULL;
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_create() failed.");
|
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
Py_ssize_t size;
|
Py_ssize_t size;
|
||||||
int set_text;
|
int *fallback = NULL;
|
||||||
|
|
||||||
if (PyUnicode_Check(string)) {
|
if (PyUnicode_Check(string)) {
|
||||||
Py_UCS4 *text = PyUnicode_AsUCS4Copy(string);
|
text = PyUnicode_AsUCS4Copy(string);
|
||||||
size = PyUnicode_GET_LENGTH(string);
|
size = PyUnicode_GET_LENGTH(string);
|
||||||
if (!text || !size) {
|
if (!text || !size) {
|
||||||
/* return 0 and clean up, no glyphs==no size,
|
/* return 0 and clean up, no glyphs==no size,
|
||||||
and raqm fails with empty strings */
|
and raqm fails with empty strings */
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
set_text = raqm_set_text(rq, text, size);
|
|
||||||
PyMem_Free(text);
|
|
||||||
} else {
|
} else {
|
||||||
char *buffer;
|
|
||||||
PyBytes_AsStringAndSize(string, &buffer, &size);
|
PyBytes_AsStringAndSize(string, &buffer, &size);
|
||||||
if (!buffer || !size) {
|
if (!buffer || !size) {
|
||||||
/* return 0 and clean up, no glyphs==no size,
|
/* return 0 and clean up, no glyphs==no size,
|
||||||
and raqm fails with empty strings */
|
and raqm fails with empty strings */
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
set_text = raqm_set_text_utf8(rq, buffer, size);
|
|
||||||
}
|
}
|
||||||
if (!set_text) {
|
if (!buffer && !text) {
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
|
PyErr_SetString(PyExc_ValueError, "expected string");
|
||||||
goto failed;
|
|
||||||
}
|
|
||||||
if (lang && !raqm_set_language(rq, lang, start, size)) {
|
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
|
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,63 +491,162 @@ text_layout_raqm(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!raqm_set_par_direction(rq, direction)) {
|
if (family->font_count > 1) {
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_set_par_direction() failed");
|
fallback = PyMem_New(int, size);
|
||||||
goto failed;
|
if (!fallback) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "failed to allocate fallback buffer.");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
for (i = 0; i < size; i++) {
|
||||||
|
fallback[i] = -2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (features != Py_None) {
|
for (face = 0;; face++) {
|
||||||
int j, len;
|
#ifdef RAQM_VERSION_ATLEAST
|
||||||
PyObject *seq = PySequence_Fast(features, "expected a sequence");
|
#if RAQM_VERSION_ATLEAST(0, 9, 0)
|
||||||
if (!seq) {
|
if (face >= 1) {
|
||||||
|
raqm_clear_contents(rq);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
if (rq != NULL) {
|
||||||
|
raqm_destroy(rq);
|
||||||
|
}
|
||||||
|
rq = raqm_create();
|
||||||
|
if (rq == NULL) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "raqm_create() failed.");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int set_text = text != NULL ? raqm_set_text(rq, text, size)
|
||||||
|
: raqm_set_text_utf8(rq, buffer, size);
|
||||||
|
if (!set_text) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "raqm_set_text() failed");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
if (lang) {
|
||||||
|
start = 0;
|
||||||
|
if (!raqm_set_language(rq, lang, start, size)) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "raqm_set_language() failed");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!raqm_set_par_direction(rq, direction)) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "raqm_set_par_direction() failed");
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
len = PySequence_Fast_GET_SIZE(seq);
|
if (features != Py_None) {
|
||||||
for (j = 0; j < len; j++) {
|
int j, len;
|
||||||
PyObject *item = PySequence_Fast_GET_ITEM(seq, j);
|
PyObject *seq = PySequence_Fast(features, "expected a sequence");
|
||||||
char *feature = NULL;
|
if (!seq) {
|
||||||
Py_ssize_t size = 0;
|
goto failed;
|
||||||
PyObject *bytes;
|
}
|
||||||
|
|
||||||
if (!PyUnicode_Check(item)) {
|
len = PySequence_Fast_GET_SIZE(seq);
|
||||||
Py_DECREF(seq);
|
for (j = 0; j < len; j++) {
|
||||||
PyErr_SetString(PyExc_TypeError, "expected a string");
|
PyObject *item = PySequence_Fast_GET_ITEM(seq, j);
|
||||||
goto failed;
|
char *feature = NULL;
|
||||||
}
|
Py_ssize_t size = 0;
|
||||||
bytes = PyUnicode_AsUTF8String(item);
|
PyObject *bytes;
|
||||||
if (bytes == NULL) {
|
|
||||||
Py_DECREF(seq);
|
if (!PyUnicode_Check(item)) {
|
||||||
goto failed;
|
Py_DECREF(seq);
|
||||||
}
|
PyErr_SetString(PyExc_TypeError, "expected a string");
|
||||||
feature = PyBytes_AS_STRING(bytes);
|
goto failed;
|
||||||
size = PyBytes_GET_SIZE(bytes);
|
}
|
||||||
if (!raqm_add_font_feature(rq, feature, size)) {
|
bytes = PyUnicode_AsUTF8String(item);
|
||||||
Py_DECREF(seq);
|
if (bytes == NULL) {
|
||||||
|
Py_DECREF(seq);
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
feature = PyBytes_AS_STRING(bytes);
|
||||||
|
size = PyBytes_GET_SIZE(bytes);
|
||||||
|
if (!raqm_add_font_feature(rq, feature, size)) {
|
||||||
|
Py_DECREF(seq);
|
||||||
|
Py_DECREF(bytes);
|
||||||
|
PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
Py_DECREF(bytes);
|
Py_DECREF(bytes);
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed");
|
}
|
||||||
|
Py_DECREF(seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (face == 0) {
|
||||||
|
if (!raqm_set_freetype_face(rq, family->faces[0])) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "raqm_set_freetype_face() failed.");
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
Py_DECREF(bytes);
|
} else {
|
||||||
|
start = 0;
|
||||||
|
/* use first font's missing glyph */
|
||||||
|
int f = face < family->font_count ? face : 0;
|
||||||
|
for (i = 1; i <= size; i++) {
|
||||||
|
if (i < size) {
|
||||||
|
if (fallback[i] == -2) {
|
||||||
|
/* not a cluster boundary */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (fallback[start] == fallback[i]) {
|
||||||
|
/* use same font face for this cluster */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fallback[start] < 0) {
|
||||||
|
raqm_set_freetype_face_range(
|
||||||
|
rq, family->faces[f], start, i - start
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
raqm_set_freetype_face_range(
|
||||||
|
rq, family->faces[fallback[start]], start, i - start
|
||||||
|
);
|
||||||
|
}
|
||||||
|
start = i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Py_DECREF(seq);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!raqm_set_freetype_face(rq, self->face)) {
|
if (!raqm_layout(rq)) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "raqm_set_freetype_face() failed.");
|
PyErr_SetString(PyExc_RuntimeError, "raqm_layout() failed.");
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!raqm_layout(rq)) {
|
glyphs = raqm_get_glyphs(rq, &count);
|
||||||
PyErr_SetString(PyExc_RuntimeError, "raqm_layout() failed.");
|
if (glyphs == NULL) {
|
||||||
goto failed;
|
PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed.");
|
||||||
}
|
count = 0;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
glyphs = raqm_get_glyphs(rq, &count);
|
if (family->font_count == 1 || face == family->font_count) {
|
||||||
if (glyphs == NULL) {
|
break;
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_get_glyphs() failed.");
|
}
|
||||||
count = 0;
|
|
||||||
goto failed;
|
for (i = 0; i < size; i++) {
|
||||||
|
if (fallback[i] == -1) {
|
||||||
|
fallback[i] = -2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int missing = 0;
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
int cluster = glyphs[i].cluster;
|
||||||
|
if (glyphs[i].index == 0) {
|
||||||
|
/* cluster contains missing glyph */
|
||||||
|
fallback[cluster] = -1;
|
||||||
|
missing = 1;
|
||||||
|
} else if (fallback[cluster] == -2) {
|
||||||
|
/* use current font face for this cluster */
|
||||||
|
fallback[cluster] = face;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!missing) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(*glyph_info) = PyMem_New(GlyphInfo, count);
|
(*glyph_info) = PyMem_New(GlyphInfo, count);
|
||||||
|
@ -386,11 +662,26 @@ text_layout_raqm(
|
||||||
(*glyph_info)[i].x_advance = glyphs[i].x_advance;
|
(*glyph_info)[i].x_advance = glyphs[i].x_advance;
|
||||||
(*glyph_info)[i].y_offset = glyphs[i].y_offset;
|
(*glyph_info)[i].y_offset = glyphs[i].y_offset;
|
||||||
(*glyph_info)[i].y_advance = glyphs[i].y_advance;
|
(*glyph_info)[i].y_advance = glyphs[i].y_advance;
|
||||||
(*glyph_info)[i].cluster = glyphs[i].cluster;
|
|
||||||
|
uint32_t cluster = glyphs[i].cluster;
|
||||||
|
(*glyph_info)[i].cluster = cluster;
|
||||||
|
if (fallback && fallback[cluster] >= 0) {
|
||||||
|
(*glyph_info)[i].face = family->faces[fallback[cluster]];
|
||||||
|
} else {
|
||||||
|
(*glyph_info)[i].face = family->faces[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
failed:
|
failed:
|
||||||
raqm_destroy(rq);
|
if (text) {
|
||||||
|
PyMem_Free(text);
|
||||||
|
}
|
||||||
|
if (fallback) {
|
||||||
|
PyMem_Free(fallback);
|
||||||
|
}
|
||||||
|
if (rq != NULL) {
|
||||||
|
raqm_destroy(rq);
|
||||||
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,7 +690,7 @@ failed:
|
||||||
static size_t
|
static size_t
|
||||||
text_layout_fallback(
|
text_layout_fallback(
|
||||||
PyObject *string,
|
PyObject *string,
|
||||||
FontObject *self,
|
FontFamily *family,
|
||||||
const char *dir,
|
const char *dir,
|
||||||
PyObject *features,
|
PyObject *features,
|
||||||
const char *lang,
|
const char *lang,
|
||||||
|
@ -407,12 +698,11 @@ text_layout_fallback(
|
||||||
int mask,
|
int mask,
|
||||||
int color
|
int color
|
||||||
) {
|
) {
|
||||||
int error, load_flags, i;
|
int error, load_flags, i, j;
|
||||||
char *buffer = NULL;
|
char *buffer = NULL;
|
||||||
FT_ULong ch;
|
FT_ULong ch;
|
||||||
Py_ssize_t count;
|
Py_ssize_t count;
|
||||||
FT_GlyphSlot glyph;
|
FT_GlyphSlot glyph;
|
||||||
FT_Bool kerning = FT_HAS_KERNING(self->face);
|
|
||||||
FT_UInt last_index = 0;
|
FT_UInt last_index = 0;
|
||||||
|
|
||||||
if (features != Py_None || dir != NULL || lang != NULL) {
|
if (features != Py_None || dir != NULL || lang != NULL) {
|
||||||
|
@ -451,34 +741,49 @@ text_layout_fallback(
|
||||||
} else {
|
} else {
|
||||||
ch = PyUnicode_READ_CHAR(string, i);
|
ch = PyUnicode_READ_CHAR(string, i);
|
||||||
}
|
}
|
||||||
(*glyph_info)[i].index = FT_Get_Char_Index(self->face, ch);
|
int found = 0;
|
||||||
error = FT_Load_Glyph(self->face, (*glyph_info)[i].index, load_flags);
|
for (j = 0; !found && j < family->font_count; j++) {
|
||||||
if (error) {
|
FT_Face face = family->faces[j];
|
||||||
geterror(error);
|
(*glyph_info)[i].index = FT_Get_Char_Index(face, ch);
|
||||||
return 0;
|
if ((*glyph_info)[i].index != 0) {
|
||||||
}
|
found = 1;
|
||||||
glyph = self->face->glyph;
|
}
|
||||||
(*glyph_info)[i].x_offset = 0;
|
/* prefer first font's missing glyph if no font support this codepoint */
|
||||||
(*glyph_info)[i].y_offset = 0;
|
if (j == 0 || found) {
|
||||||
if (kerning && last_index && (*glyph_info)[i].index) {
|
(*glyph_info)[i].face = face;
|
||||||
FT_Vector delta;
|
error = FT_Load_Glyph(face, (*glyph_info)[i].index, load_flags);
|
||||||
if (FT_Get_Kerning(
|
if (error) {
|
||||||
self->face,
|
geterror(error);
|
||||||
last_index,
|
return 0;
|
||||||
(*glyph_info)[i].index,
|
}
|
||||||
ft_kerning_default,
|
glyph = face->glyph;
|
||||||
&delta
|
(*glyph_info)[i].x_offset = 0;
|
||||||
) == 0) {
|
(*glyph_info)[i].y_offset = 0;
|
||||||
(*glyph_info)[i - 1].x_advance += PIXEL(delta.x);
|
|
||||||
(*glyph_info)[i - 1].y_advance += PIXEL(delta.y);
|
/* This has been broken and had no effect for many years now...
|
||||||
|
if (FT_HAS_KERNING(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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(*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;
|
return count;
|
||||||
}
|
}
|
||||||
|
@ -486,7 +791,7 @@ text_layout_fallback(
|
||||||
static size_t
|
static size_t
|
||||||
text_layout(
|
text_layout(
|
||||||
PyObject *string,
|
PyObject *string,
|
||||||
FontObject *self,
|
FontFamily *family,
|
||||||
const char *dir,
|
const char *dir,
|
||||||
PyObject *features,
|
PyObject *features,
|
||||||
const char *lang,
|
const char *lang,
|
||||||
|
@ -496,20 +801,20 @@ text_layout(
|
||||||
) {
|
) {
|
||||||
size_t count;
|
size_t count;
|
||||||
#ifdef HAVE_RAQM
|
#ifdef HAVE_RAQM
|
||||||
if (have_raqm && self->layout_engine == LAYOUT_RAQM) {
|
if (have_raqm && family->layout_engine == LAYOUT_RAQM) {
|
||||||
count = text_layout_raqm(string, self, dir, features, lang, glyph_info);
|
count = text_layout_raqm(string, family, dir, features, lang, glyph_info);
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
count = text_layout_fallback(
|
count = text_layout_fallback(
|
||||||
string, self, dir, features, lang, glyph_info, mask, color
|
string, family, dir, features, lang, glyph_info, mask, color
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
font_getlength(FontObject *self, PyObject *args) {
|
text_getlength(FontFamily *family, PyObject *args) {
|
||||||
int length; /* length along primary axis, in 26.6 precision */
|
int length; /* length along primary axis, in 26.6 precision */
|
||||||
GlyphInfo *glyph_info = NULL; /* computed text layout */
|
GlyphInfo *glyph_info = NULL; /* computed text layout */
|
||||||
size_t i, count; /* glyph_info index and length */
|
size_t i, count; /* glyph_info index and length */
|
||||||
|
@ -535,7 +840,8 @@ font_getlength(FontObject *self, PyObject *args) {
|
||||||
mask = mode && strcmp(mode, "1") == 0;
|
mask = mode && strcmp(mode, "1") == 0;
|
||||||
color = mode && strcmp(mode, "RGBA") == 0;
|
color = mode && strcmp(mode, "RGBA") == 0;
|
||||||
|
|
||||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
count = text_layout(string, family, dir, features, lang, &glyph_info, mask, color);
|
||||||
|
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -557,9 +863,25 @@ font_getlength(FontObject *self, PyObject *args) {
|
||||||
return PyLong_FromLong(length);
|
return PyLong_FromLong(length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
font_getlength(FontObject *self, PyObject *args) {
|
||||||
|
FontFamily family;
|
||||||
|
|
||||||
|
family.faces = &self->face;
|
||||||
|
family.font_count = 1;
|
||||||
|
family.layout_engine = self->layout_engine;
|
||||||
|
|
||||||
|
return text_getlength(&family, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
family_getlength(FontFamilyObject *self, PyObject *args) {
|
||||||
|
return text_getlength(&self->data, args);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
bounding_box_and_anchors(
|
bounding_box_and_anchors(
|
||||||
FT_Face face,
|
FontFamily *family,
|
||||||
const char *anchor,
|
const char *anchor,
|
||||||
int horizontal_dir,
|
int horizontal_dir,
|
||||||
GlyphInfo *glyph_info,
|
GlyphInfo *glyph_info,
|
||||||
|
@ -576,6 +898,7 @@ bounding_box_and_anchors(
|
||||||
int x_min, x_max, y_min, y_max; /* text bounding box, in pixels */
|
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 x_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */
|
||||||
int error;
|
int error;
|
||||||
|
FT_Face face;
|
||||||
FT_Glyph glyph;
|
FT_Glyph glyph;
|
||||||
FT_BBox bbox; /* glyph bounding box */
|
FT_BBox bbox; /* glyph bounding box */
|
||||||
size_t i; /* glyph_info index */
|
size_t i; /* glyph_info index */
|
||||||
|
@ -585,8 +908,11 @@ bounding_box_and_anchors(
|
||||||
* - pen line, i.e. 0 to `advanced` along primary axis
|
* - pen line, i.e. 0 to `advanced` along primary axis
|
||||||
* this means point (0, 0) is part of the text bounding box
|
* this means point (0, 0) is part of the text bounding box
|
||||||
*/
|
*/
|
||||||
|
face = NULL;
|
||||||
position = x_min = x_max = y_min = y_max = 0;
|
position = x_min = x_max = y_min = y_max = 0;
|
||||||
for (i = 0; i < count; i++) {
|
for (i = 0; i < count; i++) {
|
||||||
|
face = glyph_info[i].face;
|
||||||
|
|
||||||
if (horizontal_dir) {
|
if (horizontal_dir) {
|
||||||
px = PIXEL(position + glyph_info[i].x_offset);
|
px = PIXEL(position + glyph_info[i].x_offset);
|
||||||
py = PIXEL(glyph_info[i].y_offset);
|
py = PIXEL(glyph_info[i].y_offset);
|
||||||
|
@ -666,15 +992,16 @@ bounding_box_and_anchors(
|
||||||
}
|
}
|
||||||
switch (anchor[1]) {
|
switch (anchor[1]) {
|
||||||
case 'a': // ascender
|
case 'a': // ascender
|
||||||
y_anchor = PIXEL(face->size->metrics.ascender);
|
// this should be consistent with getmetrics()
|
||||||
|
y_anchor = PIXEL(family_getascender(family));
|
||||||
break;
|
break;
|
||||||
case 't': // top
|
case 't': // top
|
||||||
y_anchor = y_max;
|
y_anchor = y_max;
|
||||||
break;
|
break;
|
||||||
case 'm': // middle (ascender + descender) / 2
|
case 'm': // middle (ascender + descender) / 2
|
||||||
|
// this should be consistent with getmetrics()
|
||||||
y_anchor = PIXEL(
|
y_anchor = PIXEL(
|
||||||
(face->size->metrics.ascender + face->size->metrics.descender) /
|
(family_getascender(family) + family_getdescender(family)) / 2
|
||||||
2
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 's': // horizontal baseline
|
case 's': // horizontal baseline
|
||||||
|
@ -684,7 +1011,8 @@ bounding_box_and_anchors(
|
||||||
y_anchor = y_min;
|
y_anchor = y_min;
|
||||||
break;
|
break;
|
||||||
case 'd': // descender
|
case 'd': // descender
|
||||||
y_anchor = PIXEL(face->size->metrics.descender);
|
// this should be consistent with getmetrics()
|
||||||
|
y_anchor = PIXEL(family_getdescender(family));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
goto bad_anchor;
|
goto bad_anchor;
|
||||||
|
@ -736,7 +1064,7 @@ bad_anchor:
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
font_getsize(FontObject *self, PyObject *args) {
|
text_getsize(FontFamily *family, PyObject *args) {
|
||||||
int width, height, x_offset, y_offset;
|
int width, height, x_offset, y_offset;
|
||||||
int load_flags; /* FreeType load_flags parameter */
|
int load_flags; /* FreeType load_flags parameter */
|
||||||
int error;
|
int error;
|
||||||
|
@ -765,7 +1093,7 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
mask = mode && strcmp(mode, "1") == 0;
|
mask = mode && strcmp(mode, "1") == 0;
|
||||||
color = mode && strcmp(mode, "RGBA") == 0;
|
color = mode && strcmp(mode, "RGBA") == 0;
|
||||||
|
|
||||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
count = text_layout(string, family, dir, features, lang, &glyph_info, mask, color);
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -779,7 +1107,7 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
error = bounding_box_and_anchors(
|
error = bounding_box_and_anchors(
|
||||||
self->face,
|
family,
|
||||||
anchor,
|
anchor,
|
||||||
horizontal_dir,
|
horizontal_dir,
|
||||||
glyph_info,
|
glyph_info,
|
||||||
|
@ -802,12 +1130,29 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
font_render(FontObject *self, PyObject *args) {
|
font_getsize(FontObject *self, PyObject *args) {
|
||||||
|
FontFamily family;
|
||||||
|
|
||||||
|
family.faces = &self->face;
|
||||||
|
family.font_count = 1;
|
||||||
|
family.layout_engine = self->layout_engine;
|
||||||
|
|
||||||
|
return text_getsize(&family, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
family_getsize(FontFamilyObject *self, PyObject *args) {
|
||||||
|
return text_getsize(&self->data, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
text_render(FontFamily *family, PyObject *args) {
|
||||||
int x, y; /* pen position, in 26.6 precision */
|
int x, y; /* pen position, in 26.6 precision */
|
||||||
int px, py; /* position of current glyph, in pixels */
|
int px, py; /* position of current glyph, in pixels */
|
||||||
int x_min, y_max; /* text offset in 26.6 precision */
|
int x_min, y_max; /* text offset in 26.6 precision */
|
||||||
int load_flags; /* FreeType load_flags parameter */
|
int load_flags; /* FreeType load_flags parameter */
|
||||||
int error;
|
int error;
|
||||||
|
FT_Face face;
|
||||||
FT_Glyph glyph;
|
FT_Glyph glyph;
|
||||||
FT_GlyphSlot glyph_slot;
|
FT_GlyphSlot glyph_slot;
|
||||||
FT_Bitmap bitmap;
|
FT_Bitmap bitmap;
|
||||||
|
@ -869,19 +1214,22 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
foreground_ink = foreground_ink_long;
|
foreground_ink = foreground_ink_long;
|
||||||
|
|
||||||
#ifdef FT_COLOR_H
|
#ifdef FT_COLOR_H
|
||||||
if (color) {
|
for (int i = 0; i < family->font_count; i++) {
|
||||||
FT_Color foreground_color;
|
if (color) {
|
||||||
FT_Byte *ink = (FT_Byte *)&foreground_ink;
|
FT_Color foreground_color;
|
||||||
foreground_color.red = ink[0];
|
FT_Byte *ink = (FT_Byte *)&foreground_ink;
|
||||||
foreground_color.green = ink[1];
|
foreground_color.red = ink[0];
|
||||||
foreground_color.blue = ink[2];
|
foreground_color.green = ink[1];
|
||||||
foreground_color.alpha =
|
foreground_color.blue = ink[2];
|
||||||
(FT_Byte)255; /* ink alpha is handled in ImageDraw.text */
|
foreground_color.alpha =
|
||||||
FT_Palette_Set_Foreground_Color(self->face, foreground_color);
|
(FT_Byte)255; /* ink alpha is handled in ImageDraw.text */
|
||||||
|
FT_Palette_Set_Foreground_Color(family->faces[i], foreground_color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask, color);
|
count = text_layout(string, family, dir, features, lang, &glyph_info, mask, color);
|
||||||
|
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -897,7 +1245,7 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||||
|
|
||||||
error = bounding_box_and_anchors(
|
error = bounding_box_and_anchors(
|
||||||
self->face,
|
family,
|
||||||
anchor,
|
anchor,
|
||||||
horizontal_dir,
|
horizontal_dir,
|
||||||
glyph_info,
|
glyph_info,
|
||||||
|
@ -960,14 +1308,14 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
px = PIXEL(x + glyph_info[i].x_offset);
|
px = PIXEL(x + glyph_info[i].x_offset);
|
||||||
py = PIXEL(y + glyph_info[i].y_offset);
|
py = PIXEL(y + glyph_info[i].y_offset);
|
||||||
|
|
||||||
error =
|
face = glyph_info[i].face;
|
||||||
FT_Load_Glyph(self->face, glyph_info[i].index, load_flags | FT_LOAD_RENDER);
|
error = FT_Load_Glyph(face, glyph_info[i].index, load_flags | FT_LOAD_RENDER);
|
||||||
if (error) {
|
if (error) {
|
||||||
geterror(error);
|
geterror(error);
|
||||||
goto glyph_error;
|
goto glyph_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
glyph_slot = self->face->glyph;
|
glyph_slot = face->glyph;
|
||||||
bitmap = glyph_slot->bitmap;
|
bitmap = glyph_slot->bitmap;
|
||||||
|
|
||||||
if (glyph_slot->bitmap_top + py > y_max) {
|
if (glyph_slot->bitmap_top + py > y_max) {
|
||||||
|
@ -993,13 +1341,14 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
px = PIXEL(x + glyph_info[i].x_offset);
|
px = PIXEL(x + glyph_info[i].x_offset);
|
||||||
py = PIXEL(y + glyph_info[i].y_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) {
|
if (error) {
|
||||||
geterror(error);
|
geterror(error);
|
||||||
goto glyph_error;
|
goto glyph_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
glyph_slot = self->face->glyph;
|
glyph_slot = face->glyph;
|
||||||
if (stroker != NULL) {
|
if (stroker != NULL) {
|
||||||
error = FT_Get_Glyph(glyph_slot, &glyph);
|
error = FT_Get_Glyph(glyph_slot, &glyph);
|
||||||
if (!error) {
|
if (!error) {
|
||||||
|
@ -1214,6 +1563,22 @@ glyph_error:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
font_render(FontObject *self, PyObject *args) {
|
||||||
|
FontFamily family;
|
||||||
|
|
||||||
|
family.faces = &self->face;
|
||||||
|
family.font_count = 1;
|
||||||
|
family.layout_engine = self->layout_engine;
|
||||||
|
|
||||||
|
return text_render(&family, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
family_render(FontFamilyObject *self, PyObject *args) {
|
||||||
|
return text_render(&self->data, args);
|
||||||
|
}
|
||||||
|
|
||||||
#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \
|
#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \
|
||||||
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
|
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -1545,8 +1910,102 @@ static PyTypeObject Font_Type = {
|
||||||
font_getsetters, /*tp_getset*/
|
font_getsetters, /*tp_getset*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
family_dealloc(FontFamilyObject *self) {
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < self->data.font_count; i++) {
|
||||||
|
if (self->data.faces[i]) {
|
||||||
|
FT_Done_Face(self->data.faces[i]);
|
||||||
|
}
|
||||||
|
if (self->font_bytes[i]) {
|
||||||
|
PyMem_Free(self->font_bytes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PyMem_Free(self->data.faces);
|
||||||
|
PyMem_Free(self->font_bytes);
|
||||||
|
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 PyObject *
|
||||||
|
family_getattr_ascent(FontFamilyObject *self, void *closure) {
|
||||||
|
return PyLong_FromLong(PIXEL(family_getascender(&self->data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
family_getattr_descent(FontFamilyObject *self, void *closure) {
|
||||||
|
return PyLong_FromLong(-PIXEL(family_getdescender(&self->data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
family_getattr_height(FontFamilyObject *self, void *closure) {
|
||||||
|
return PyLong_FromLong(PIXEL(family_getheight(&self->data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct PyGetSetDef family_getsetters[] = {
|
||||||
|
//{"family", (getter)font_getattr_family},
|
||||||
|
//{"style", (getter)font_getattr_style},
|
||||||
|
{"ascent", (getter)family_getattr_ascent},
|
||||||
|
{"descent", (getter)family_getattr_descent},
|
||||||
|
{"height", (getter)family_getattr_height},
|
||||||
|
//{"x_ppem", (getter)font_getattr_x_ppem},
|
||||||
|
//{"y_ppem", (getter)font_getattr_y_ppem},
|
||||||
|
//{"glyphs", (getter)font_getattr_glyphs},
|
||||||
|
{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*/
|
||||||
|
family_getsetters, /* tp_getset*/
|
||||||
|
};
|
||||||
|
|
||||||
static PyMethodDef _functions[] = {
|
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
|
static int
|
||||||
|
@ -1559,6 +2018,7 @@ setup_module(PyObject *m) {
|
||||||
|
|
||||||
/* Ready object type */
|
/* Ready object type */
|
||||||
PyType_Ready(&Font_Type);
|
PyType_Ready(&Font_Type);
|
||||||
|
PyType_Ready(&FontFamily_Type);
|
||||||
|
|
||||||
if (FT_Init_FreeType(&library)) {
|
if (FT_Init_FreeType(&library)) {
|
||||||
return 0; /* leave it uninitialized */
|
return 0; /* leave it uninitialized */
|
||||||
|
|
Loading…
Reference in New Issue
Block a user