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
|
||||
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
|
||||
----------------------------------
|
||||
|
||||
|
@ -295,18 +350,27 @@ Methods
|
|||
|
||||
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,
|
||||
the text is passed on to
|
||||
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`.
|
||||
:param fill: Color to use for the text.
|
||||
: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
|
||||
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`,
|
||||
the number of pixels between lines.
|
||||
:param align: If the text is passed on to
|
||||
: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
|
||||
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
||||
Requires libraqm.
|
||||
|
@ -347,12 +411,22 @@ Methods
|
|||
|
||||
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 fill: Color to use for the text.
|
||||
: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 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
|
||||
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
||||
Requires libraqm.
|
||||
|
|
|
@ -320,6 +320,7 @@ class ImageDraw:
|
|||
features=features,
|
||||
language=language,
|
||||
stroke_width=stroke_width,
|
||||
anchor=anchor,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
@ -333,6 +334,7 @@ class ImageDraw:
|
|||
features,
|
||||
language,
|
||||
stroke_width,
|
||||
anchor,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
@ -353,7 +355,7 @@ class ImageDraw:
|
|||
draw_text(stroke_ink, stroke_width)
|
||||
|
||||
# Draw normal text
|
||||
draw_text(ink, 0, (stroke_width, stroke_width))
|
||||
draw_text(ink, 0)
|
||||
else:
|
||||
# Only draw normal text
|
||||
draw_text(ink)
|
||||
|
@ -373,6 +375,16 @@ class ImageDraw:
|
|||
stroke_width=0,
|
||||
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 = []
|
||||
max_width = 0
|
||||
lines = self._multiline_split(text)
|
||||
|
@ -390,16 +402,33 @@ class ImageDraw:
|
|||
)
|
||||
widths.append(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):
|
||||
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":
|
||||
pass # left = x
|
||||
pass
|
||||
elif align == "center":
|
||||
left += (max_width - widths[idx]) / 2.0
|
||||
left += width_difference / 2.0
|
||||
elif align == "right":
|
||||
left += max_width - widths[idx]
|
||||
left += width_difference
|
||||
else:
|
||||
raise ValueError('align must be "left", "center" or "right"')
|
||||
|
||||
self.text(
|
||||
(left, top),
|
||||
line,
|
||||
|
@ -413,7 +442,6 @@ class ImageDraw:
|
|||
stroke_fill=stroke_fill,
|
||||
)
|
||||
top += line_spacing
|
||||
left = xy[0]
|
||||
|
||||
def textsize(
|
||||
self,
|
||||
|
|
|
@ -347,6 +347,7 @@ class FreeTypeFont:
|
|||
features=None,
|
||||
language=None,
|
||||
stroke_width=0,
|
||||
anchor=None,
|
||||
):
|
||||
"""
|
||||
Create a bitmap for the text.
|
||||
|
@ -395,6 +396,12 @@ class FreeTypeFont:
|
|||
|
||||
.. 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
|
||||
:py:mod:`PIL.Image.core` interface module.
|
||||
"""
|
||||
|
@ -405,6 +412,7 @@ class FreeTypeFont:
|
|||
features=features,
|
||||
language=language,
|
||||
stroke_width=stroke_width,
|
||||
anchor=anchor,
|
||||
)[0]
|
||||
|
||||
def getmask2(
|
||||
|
@ -416,6 +424,7 @@ class FreeTypeFont:
|
|||
features=None,
|
||||
language=None,
|
||||
stroke_width=0,
|
||||
anchor=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
|
@ -466,14 +475,21 @@ class FreeTypeFont:
|
|||
|
||||
.. 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
|
||||
:py:mod:`PIL.Image.core` interface module, and the text offset, the
|
||||
gap between the starting coordinate and the first marking
|
||||
"""
|
||||
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
|
||||
offset = offset[0] - stroke_width, offset[1] - stroke_width
|
||||
im = fill("L", size, 0)
|
||||
self.font.render(
|
||||
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? */
|
||||
const char *dir = NULL;
|
||||
const char *lang = NULL;
|
||||
const char *anchor = NULL;
|
||||
PyObject *features = Py_None;
|
||||
PyObject *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;
|
||||
}
|
||||
|
||||
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);
|
||||
if (PyErr_Occurred()) {
|
||||
return NULL;
|
||||
|
@ -720,11 +728,75 @@ font_getsize(FontObject* self, PyObject* args)
|
|||
x_anchor = y_anchor = 0;
|
||||
if (face) {
|
||||
if (horizontal_dir) {
|
||||
x_anchor = 0;
|
||||
y_anchor = PIXEL(self->face->size->metrics.ascender);
|
||||
switch (anchor[0]) {
|
||||
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 {
|
||||
x_anchor = x_min;
|
||||
y_anchor = 0;
|
||||
switch (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_anchor + x_min), -(-y_anchor + y_max)
|
||||
);
|
||||
|
||||
bad_anchor:
|
||||
PyErr_Format(PyExc_ValueError, "bad anchor specified: %s", anchor);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
|
|
Loading…
Reference in New Issue
Block a user