mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 17:36:18 +03:00
implement text anchor for truetype fonts
(cherry picked from commit bac9025918ccf944bac77addc130f33cf9d74701)
This commit is contained in:
parent
c2367400fa
commit
e6d4c2ce8f
|
@ -74,6 +74,61 @@ To load a OpenType/TrueType font, use the truetype function in the
|
||||||
:py:mod:`~PIL.ImageFont` module. Note that this function depends on third-party
|
:py:mod:`~PIL.ImageFont` module. Note that this function depends on third-party
|
||||||
libraries, and may not available in all PIL builds.
|
libraries, and may not available in all PIL builds.
|
||||||
|
|
||||||
|
Text anchors
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The ``anchor`` parameter determines the position of the ``xy`` coordinates relative to the text.
|
||||||
|
It consists of two characters, the horizontal and vertical alignment.
|
||||||
|
|
||||||
|
The default value is ``la`` for horizontal text and ``lt`` for vertical text.
|
||||||
|
|
||||||
|
This parameter is ignored for legacy PIL fonts, where the anchor is always top-left.
|
||||||
|
|
||||||
|
+---+-----------------------+-------------------------------------------------------+
|
||||||
|
| Horizontal anchor alignment |
|
||||||
|
+===+=======================+=======================================================+
|
||||||
|
| l | left | Anchor is to the left of the text. |
|
||||||
|
+---+-----------------------+-------------------------------------------------------+
|
||||||
|
| m | middle | Anchor is horizontally centered with the text. |
|
||||||
|
+---+-----------------------+-------------------------------------------------------+
|
||||||
|
| r | right | Anchor is to the right of the text. |
|
||||||
|
+---+-----------------------+-------------------------------------------------------+
|
||||||
|
| s | baseline | **(vertical text only)** |
|
||||||
|
| | | Anchor is at the baseline (middle) of the text. |
|
||||||
|
| | | The exact alignment depends on the font. |
|
||||||
|
+---+-----------------------+-------------------------------------------------------+
|
||||||
|
|
||||||
|
+---+-----------------------+-------------------------------------------------------+
|
||||||
|
| Vertical anchor alignment |
|
||||||
|
+===+=======================+=======================================================+
|
||||||
|
| a | ascender (top) | **(horizontal text only)** |
|
||||||
|
| | | Anchor is at the ascender line (top) |
|
||||||
|
| | | of the first line of text. |
|
||||||
|
+---+-----------------------+-------------------------------------------------------+
|
||||||
|
| t | top | **(single-line text only)** |
|
||||||
|
| | | Anchor is at the top of the text. |
|
||||||
|
+---+-----------------------+-------------------------------------------------------+
|
||||||
|
| m | middle | Anchor is vertically centered with the text. |
|
||||||
|
| | | For horizontal text this is the midpoint of the |
|
||||||
|
| | | first ascender line and the last descender line. |
|
||||||
|
+---+-----------------------+-------------------------------------------------------+
|
||||||
|
| s | baseline | **(horizontal text only)** |
|
||||||
|
| | | Anchor is at the baseline (bottom) |
|
||||||
|
| | | of the first line of text, only |
|
||||||
|
| | | descenders extend below the anchor. |
|
||||||
|
+---+-----------------------+-------------------------------------------------------+
|
||||||
|
| b | bottom | **(single-line text only)** |
|
||||||
|
| | | Anchor is at the bottom of the text. |
|
||||||
|
+---+-----------------------+-------------------------------------------------------+
|
||||||
|
| d | descender (bottom) | **(horizontal text only)** |
|
||||||
|
| | | Anchor is at the descender line (bottom) |
|
||||||
|
| | | of the last line of text. |
|
||||||
|
+---+-----------------------+-------------------------------------------------------+
|
||||||
|
|
||||||
|
See `Font metrics on Wikipedia <https://en.wikipedia.org/wiki/Typeface#Font_metrics>`_
|
||||||
|
for more information on the specific terms used.
|
||||||
|
|
||||||
|
|
||||||
Example: Draw Partial Opacity Text
|
Example: Draw Partial Opacity Text
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
|
@ -295,18 +350,27 @@ Methods
|
||||||
|
|
||||||
Draws the string at the given position.
|
Draws the string at the given position.
|
||||||
|
|
||||||
:param xy: Top left corner of the text.
|
:param xy: The anchor coordinates of the text.
|
||||||
:param text: Text to be drawn. If it contains any newline characters,
|
:param text: Text to be drawn. If it contains any newline characters,
|
||||||
the text is passed on to
|
the text is passed on to
|
||||||
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`.
|
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`.
|
||||||
:param fill: Color to use for the text.
|
:param fill: Color to use for the text.
|
||||||
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
||||||
|
: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. This parameter is
|
||||||
|
ignored for legacy PIL fonts.
|
||||||
|
|
||||||
|
.. note:: This parameter was present in earlier versions
|
||||||
|
of Pillow, but implemented only in version 7.2.0.
|
||||||
|
|
||||||
:param spacing: If the text is passed on to
|
:param spacing: If the text is passed on to
|
||||||
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`,
|
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`,
|
||||||
the number of pixels between lines.
|
the number of pixels between lines.
|
||||||
:param align: If the text is passed on to
|
:param align: If the text is passed on to
|
||||||
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`,
|
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`,
|
||||||
``"left"``, ``"center"`` or ``"right"``.
|
``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines.
|
||||||
|
Use the ``anchor`` parameter to specify the alignment to ``xy``.
|
||||||
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
||||||
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
||||||
Requires libraqm.
|
Requires libraqm.
|
||||||
|
@ -347,12 +411,22 @@ Methods
|
||||||
|
|
||||||
Draws the string at the given position.
|
Draws the string at the given position.
|
||||||
|
|
||||||
:param xy: Top left corner of the text.
|
:param xy: The anchor coordinates of the text.
|
||||||
:param text: Text to be drawn.
|
:param text: Text to be drawn.
|
||||||
:param fill: Color to use for the text.
|
:param fill: Color to use for the text.
|
||||||
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
||||||
|
|
||||||
|
: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. This parameter is
|
||||||
|
ignored for legacy PIL fonts.
|
||||||
|
|
||||||
|
.. note:: This parameter was present in earlier versions
|
||||||
|
of Pillow, but implemented only in version 7.2.0.
|
||||||
|
|
||||||
:param spacing: The number of pixels between lines.
|
:param spacing: The number of pixels between lines.
|
||||||
:param align: ``"left"``, ``"center"`` or ``"right"``.
|
:param align: ``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines.
|
||||||
|
Use the ``anchor`` parameter to specify the alignment to ``xy``.
|
||||||
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
||||||
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
||||||
Requires libraqm.
|
Requires libraqm.
|
||||||
|
|
|
@ -320,6 +320,7 @@ class ImageDraw:
|
||||||
features=features,
|
features=features,
|
||||||
language=language,
|
language=language,
|
||||||
stroke_width=stroke_width,
|
stroke_width=stroke_width,
|
||||||
|
anchor=anchor,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
@ -333,6 +334,7 @@ class ImageDraw:
|
||||||
features,
|
features,
|
||||||
language,
|
language,
|
||||||
stroke_width,
|
stroke_width,
|
||||||
|
anchor,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
@ -353,7 +355,7 @@ class ImageDraw:
|
||||||
draw_text(stroke_ink, stroke_width)
|
draw_text(stroke_ink, stroke_width)
|
||||||
|
|
||||||
# Draw normal text
|
# Draw normal text
|
||||||
draw_text(ink, 0, (stroke_width, stroke_width))
|
draw_text(ink, 0)
|
||||||
else:
|
else:
|
||||||
# Only draw normal text
|
# Only draw normal text
|
||||||
draw_text(ink)
|
draw_text(ink)
|
||||||
|
@ -373,6 +375,16 @@ class ImageDraw:
|
||||||
stroke_width=0,
|
stroke_width=0,
|
||||||
stroke_fill=None,
|
stroke_fill=None,
|
||||||
):
|
):
|
||||||
|
if direction == "ttb":
|
||||||
|
raise ValueError("ttb direction is unsupported for multiline text")
|
||||||
|
|
||||||
|
if anchor is None:
|
||||||
|
anchor = "la"
|
||||||
|
elif len(anchor) != 2:
|
||||||
|
raise ValueError("anchor must be a 2 character string")
|
||||||
|
elif anchor[1] in "tb":
|
||||||
|
raise ValueError("anchor not supported for multiline text")
|
||||||
|
|
||||||
widths = []
|
widths = []
|
||||||
max_width = 0
|
max_width = 0
|
||||||
lines = self._multiline_split(text)
|
lines = self._multiline_split(text)
|
||||||
|
@ -390,16 +402,33 @@ class ImageDraw:
|
||||||
)
|
)
|
||||||
widths.append(line_width)
|
widths.append(line_width)
|
||||||
max_width = max(max_width, line_width)
|
max_width = max(max_width, line_width)
|
||||||
left, top = xy
|
|
||||||
|
top = xy[1]
|
||||||
|
if anchor[1] == "m":
|
||||||
|
top -= (len(lines) - 1) * line_spacing / 2.0
|
||||||
|
elif anchor[1] == "d":
|
||||||
|
top -= (len(lines) - 1) * line_spacing
|
||||||
|
|
||||||
for idx, line in enumerate(lines):
|
for idx, line in enumerate(lines):
|
||||||
|
left = xy[0]
|
||||||
|
width_difference = max_width - widths[idx]
|
||||||
|
|
||||||
|
# first align left by anchor
|
||||||
|
if anchor[0] == "m":
|
||||||
|
left -= width_difference / 2.0
|
||||||
|
elif anchor[0] == "r":
|
||||||
|
left -= width_difference
|
||||||
|
|
||||||
|
# then align by align parameter
|
||||||
if align == "left":
|
if align == "left":
|
||||||
pass # left = x
|
pass
|
||||||
elif align == "center":
|
elif align == "center":
|
||||||
left += (max_width - widths[idx]) / 2.0
|
left += width_difference / 2.0
|
||||||
elif align == "right":
|
elif align == "right":
|
||||||
left += max_width - widths[idx]
|
left += width_difference
|
||||||
else:
|
else:
|
||||||
raise ValueError('align must be "left", "center" or "right"')
|
raise ValueError('align must be "left", "center" or "right"')
|
||||||
|
|
||||||
self.text(
|
self.text(
|
||||||
(left, top),
|
(left, top),
|
||||||
line,
|
line,
|
||||||
|
@ -413,7 +442,6 @@ class ImageDraw:
|
||||||
stroke_fill=stroke_fill,
|
stroke_fill=stroke_fill,
|
||||||
)
|
)
|
||||||
top += line_spacing
|
top += line_spacing
|
||||||
left = xy[0]
|
|
||||||
|
|
||||||
def textsize(
|
def textsize(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -347,6 +347,7 @@ class FreeTypeFont:
|
||||||
features=None,
|
features=None,
|
||||||
language=None,
|
language=None,
|
||||||
stroke_width=0,
|
stroke_width=0,
|
||||||
|
anchor=None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Create a bitmap for the text.
|
Create a bitmap for the text.
|
||||||
|
@ -395,6 +396,12 @@ class FreeTypeFont:
|
||||||
|
|
||||||
.. versionadded:: 6.2.0
|
.. 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
|
||||||
|
|
||||||
:return: An internal PIL storage memory instance as defined by the
|
:return: An internal PIL storage memory instance as defined by the
|
||||||
:py:mod:`PIL.Image.core` interface module.
|
:py:mod:`PIL.Image.core` interface module.
|
||||||
"""
|
"""
|
||||||
|
@ -405,6 +412,7 @@ class FreeTypeFont:
|
||||||
features=features,
|
features=features,
|
||||||
language=language,
|
language=language,
|
||||||
stroke_width=stroke_width,
|
stroke_width=stroke_width,
|
||||||
|
anchor=anchor,
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
def getmask2(
|
def getmask2(
|
||||||
|
@ -416,6 +424,7 @@ class FreeTypeFont:
|
||||||
features=None,
|
features=None,
|
||||||
language=None,
|
language=None,
|
||||||
stroke_width=0,
|
stroke_width=0,
|
||||||
|
anchor=None,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
|
@ -466,14 +475,21 @@ class FreeTypeFont:
|
||||||
|
|
||||||
.. versionadded:: 6.2.0
|
.. 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
|
||||||
|
|
||||||
:return: A tuple of an internal PIL storage memory instance as defined by the
|
: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
|
:py:mod:`PIL.Image.core` interface module, and the text offset, the
|
||||||
gap between the starting coordinate and the first marking
|
gap between the starting coordinate and the first marking
|
||||||
"""
|
"""
|
||||||
size, offset = self.font.getsize(
|
size, offset = self.font.getsize(
|
||||||
text, mode == "1", direction, features, language
|
text, mode == "1", direction, features, language, anchor
|
||||||
)
|
)
|
||||||
size = size[0] + stroke_width * 2, size[1] + stroke_width * 2
|
size = size[0] + stroke_width * 2, size[1] + stroke_width * 2
|
||||||
|
offset = offset[0] - stroke_width, offset[1] - stroke_width
|
||||||
im = fill("L", size, 0)
|
im = fill("L", size, 0)
|
||||||
self.font.render(
|
self.font.render(
|
||||||
text, im.id, mode == "1", direction, features, language, stroke_width
|
text, im.id, mode == "1", direction, features, language, stroke_width
|
||||||
|
|
|
@ -626,17 +626,25 @@ font_getsize(FontObject* self, PyObject* args)
|
||||||
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */
|
||||||
const char *dir = NULL;
|
const char *dir = NULL;
|
||||||
const char *lang = NULL;
|
const char *lang = NULL;
|
||||||
|
const char *anchor = NULL;
|
||||||
PyObject *features = Py_None;
|
PyObject *features = Py_None;
|
||||||
PyObject *string;
|
PyObject *string;
|
||||||
|
|
||||||
/* calculate size and bearing for a given string */
|
/* calculate size and bearing for a given string */
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O|izOz:getsize", &string, &mask, &dir, &features, &lang)) {
|
if (!PyArg_ParseTuple(args, "O|izOzz:getsize", &string, &mask, &dir, &features, &lang, &anchor)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||||
|
|
||||||
|
if (anchor == NULL) {
|
||||||
|
anchor = horizontal_dir ? "la" : "lt";
|
||||||
|
}
|
||||||
|
if (strlen(anchor) != 2) {
|
||||||
|
goto bad_anchor;
|
||||||
|
}
|
||||||
|
|
||||||
count = text_layout(string, self, dir, features, lang, &glyph_info, mask);
|
count = text_layout(string, self, dir, features, lang, &glyph_info, mask);
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -720,11 +728,75 @@ font_getsize(FontObject* self, PyObject* args)
|
||||||
x_anchor = y_anchor = 0;
|
x_anchor = y_anchor = 0;
|
||||||
if (face) {
|
if (face) {
|
||||||
if (horizontal_dir) {
|
if (horizontal_dir) {
|
||||||
x_anchor = 0;
|
switch (anchor[0]) {
|
||||||
y_anchor = PIXEL(self->face->size->metrics.ascender);
|
case 'l': // left
|
||||||
|
x_anchor = 0;
|
||||||
|
break;
|
||||||
|
case 'm': // middle (left + right) / 2
|
||||||
|
x_anchor = PIXEL(position / 2);
|
||||||
|
break;
|
||||||
|
case 'r': // right
|
||||||
|
x_anchor = PIXEL(position);
|
||||||
|
break;
|
||||||
|
case 's': // vertical baseline
|
||||||
|
default:
|
||||||
|
goto bad_anchor;
|
||||||
|
}
|
||||||
|
switch (anchor[1]) {
|
||||||
|
case 'a': // ascender
|
||||||
|
y_anchor = PIXEL(self->face->size->metrics.ascender);
|
||||||
|
break;
|
||||||
|
case 't': // top
|
||||||
|
y_anchor = y_max;
|
||||||
|
break;
|
||||||
|
case 'm': // middle (ascender + descender) / 2
|
||||||
|
y_anchor = PIXEL((self->face->size->metrics.ascender + self->face->size->metrics.descender) / 2);
|
||||||
|
break;
|
||||||
|
case 's': // horizontal baseline
|
||||||
|
y_anchor = 0;
|
||||||
|
break;
|
||||||
|
case 'b': // bottom
|
||||||
|
y_anchor = y_min;
|
||||||
|
break;
|
||||||
|
case 'd': // descender
|
||||||
|
y_anchor = PIXEL(self->face->size->metrics.descender);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
goto bad_anchor;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
x_anchor = x_min;
|
switch (anchor[0]) {
|
||||||
y_anchor = 0;
|
case 'l': // left
|
||||||
|
x_anchor = x_min;
|
||||||
|
break;
|
||||||
|
case 'm': // middle (left + right) / 2
|
||||||
|
x_anchor = (x_min + x_max) / 2;
|
||||||
|
break;
|
||||||
|
case 'r': // right
|
||||||
|
x_anchor = x_max;
|
||||||
|
break;
|
||||||
|
case 's': // vertical baseline
|
||||||
|
x_anchor = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
goto bad_anchor;
|
||||||
|
}
|
||||||
|
switch (anchor[1]) {
|
||||||
|
case 't': // top
|
||||||
|
y_anchor = 0;
|
||||||
|
break;
|
||||||
|
case 'm': // middle (top + bottom) / 2
|
||||||
|
y_anchor = PIXEL(position / 2);
|
||||||
|
break;
|
||||||
|
case 'b': // bottom
|
||||||
|
y_anchor = PIXEL(position);
|
||||||
|
break;
|
||||||
|
case 'a': // ascender
|
||||||
|
case 's': // horizontal baseline
|
||||||
|
case 'd': // descender
|
||||||
|
default:
|
||||||
|
goto bad_anchor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -733,6 +805,10 @@ font_getsize(FontObject* self, PyObject* args)
|
||||||
(x_max - x_min), (y_max - y_min),
|
(x_max - x_min), (y_max - y_min),
|
||||||
(-x_anchor + x_min), -(-y_anchor + y_max)
|
(-x_anchor + x_min), -(-y_anchor + y_max)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bad_anchor:
|
||||||
|
PyErr_Format(PyExc_ValueError, "bad anchor specified: %s", anchor);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
|
|
Loading…
Reference in New Issue
Block a user