mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-22 21:24:46 +03:00
implement text anchor for truetype fonts
This commit is contained in:
parent
5e271e2a6c
commit
bd693f7ecf
|
@ -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
|
||||
----------------------------------
|
||||
|
||||
|
@ -277,18 +332,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.
|
||||
|
@ -329,12 +393,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.
|
||||
|
|
|
@ -314,6 +314,7 @@ class ImageDraw:
|
|||
features=features,
|
||||
language=language,
|
||||
stroke_width=stroke_width,
|
||||
anchor=anchor,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
@ -327,6 +328,7 @@ class ImageDraw:
|
|||
features,
|
||||
language,
|
||||
stroke_width,
|
||||
anchor,
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
@ -347,7 +349,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)
|
||||
|
@ -367,6 +369,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)
|
||||
|
@ -384,16 +396,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,
|
||||
|
@ -407,7 +436,6 @@ class ImageDraw:
|
|||
stroke_fill=stroke_fill,
|
||||
)
|
||||
top += line_spacing
|
||||
left = xy[0]
|
||||
|
||||
def textsize(
|
||||
self,
|
||||
|
|
|
@ -345,6 +345,7 @@ class FreeTypeFont:
|
|||
features=None,
|
||||
language=None,
|
||||
stroke_width=0,
|
||||
anchor=None,
|
||||
):
|
||||
"""
|
||||
Create a bitmap for the text.
|
||||
|
@ -393,6 +394,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:: 7.2.0
|
||||
|
||||
:return: An internal PIL storage memory instance as defined by the
|
||||
:py:mod:`PIL.Image.core` interface module.
|
||||
"""
|
||||
|
@ -403,6 +410,7 @@ class FreeTypeFont:
|
|||
features=features,
|
||||
language=language,
|
||||
stroke_width=stroke_width,
|
||||
anchor=anchor,
|
||||
)[0]
|
||||
|
||||
def getmask2(
|
||||
|
@ -414,6 +422,7 @@ class FreeTypeFont:
|
|||
features=None,
|
||||
language=None,
|
||||
stroke_width=0,
|
||||
anchor=None,
|
||||
*args,
|
||||
**kwargs
|
||||
):
|
||||
|
@ -464,14 +473,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:: 7.2.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
|
||||
|
|
|
@ -612,12 +612,13 @@ font_getsize(FontObject* self, PyObject* args)
|
|||
int position, advanced;
|
||||
int x_max, x_min, y_max, y_min;
|
||||
FT_Face face;
|
||||
int xoffset, yoffset;
|
||||
int x_anchor, y_anchor;
|
||||
int horizontal_dir;
|
||||
int mask = 0;
|
||||
int load_flags;
|
||||
const char *dir = NULL;
|
||||
const char *lang = NULL;
|
||||
const char *anchor = NULL;
|
||||
size_t i, count;
|
||||
GlyphInfo *glyph_info = NULL;
|
||||
PyObject *features = Py_None;
|
||||
|
@ -625,10 +626,19 @@ font_getsize(FontObject* self, PyObject* args)
|
|||
/* calculate size and bearing for a given string */
|
||||
|
||||
PyObject* string;
|
||||
if (!PyArg_ParseTuple(args, "O|izOz:getsize", &string, &mask, &dir, &features, &lang)) {
|
||||
if (!PyArg_ParseTuple(args, "O|izOzzz: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;
|
||||
|
@ -636,7 +646,6 @@ font_getsize(FontObject* self, PyObject* args)
|
|||
|
||||
face = NULL;
|
||||
position = x_max = x_min = y_max = y_min = 0;
|
||||
horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1;
|
||||
for (i = 0; i < count; i++) {
|
||||
int index, error, offset;
|
||||
FT_BBox bbox;
|
||||
|
@ -733,21 +742,90 @@ font_getsize(FontObject* self, PyObject* args)
|
|||
glyph_info = NULL;
|
||||
}
|
||||
|
||||
x_anchor = y_anchor = 0;
|
||||
if (face) {
|
||||
if (horizontal_dir) {
|
||||
xoffset = 0;
|
||||
yoffset = self->face->size->metrics.ascender - y_max;
|
||||
switch (anchor[0]) {
|
||||
case 'l': // left
|
||||
x_anchor = 0;
|
||||
break;
|
||||
case 'm': // middle (left + right) / 2
|
||||
x_anchor = position / 2;
|
||||
break;
|
||||
case 'r': // right
|
||||
x_anchor = position;
|
||||
break;
|
||||
case 's': // vertical baseline
|
||||
default:
|
||||
goto bad_anchor;
|
||||
}
|
||||
switch (anchor[1]) {
|
||||
case 'a': // ascender
|
||||
y_anchor = self->face->size->metrics.ascender;
|
||||
break;
|
||||
case 't': // top
|
||||
y_anchor = y_max;
|
||||
break;
|
||||
case 'm': // middle (ascender + descender) / 2
|
||||
y_anchor = (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 = self->face->size->metrics.descender;
|
||||
break;
|
||||
default:
|
||||
goto bad_anchor;
|
||||
}
|
||||
} else {
|
||||
xoffset = 0;
|
||||
yoffset = -y_max;
|
||||
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 = position / 2;
|
||||
break;
|
||||
case 'b': // bottom
|
||||
y_anchor = position;
|
||||
break;
|
||||
case 'a': // ascender
|
||||
case 's': // horizontal baseline
|
||||
case 'd': // descender
|
||||
default:
|
||||
goto bad_anchor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Py_BuildValue(
|
||||
"(ii)(ii)",
|
||||
PIXEL(x_max - x_min), PIXEL(y_max - y_min),
|
||||
PIXEL(xoffset), PIXEL(yoffset)
|
||||
);
|
||||
PIXEL(-x_anchor + x_min), PIXEL(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