From 9947d70c480c6d9a7c281351aba12159016fd524 Mon Sep 17 00:00:00 2001 From: nulano Date: Thu, 23 Apr 2020 05:38:41 +0200 Subject: [PATCH] write anchor docs --- docs/example/anchors.png | Bin 0 -> 13786 bytes docs/example/anchors.py | 29 + docs/handbook/appendices.rst | 1 + docs/handbook/text-anchors.rst | 140 +++++ docs/reference/ImageDraw.rst | 64 +- docs/resources/anchor_horizontal.svg | 467 +++++++++++++++ docs/resources/anchor_vertical.svg | 841 +++++++++++++++++++++++++++ src/PIL/ImageFont.py | 4 +- 8 files changed, 1485 insertions(+), 61 deletions(-) create mode 100644 docs/example/anchors.png create mode 100644 docs/example/anchors.py create mode 100644 docs/handbook/text-anchors.rst create mode 100644 docs/resources/anchor_horizontal.svg create mode 100644 docs/resources/anchor_vertical.svg diff --git a/docs/example/anchors.png b/docs/example/anchors.png new file mode 100644 index 0000000000000000000000000000000000000000..37b4d8219052ab3250afa273d5e99b0f2a2c47f3 GIT binary patch literal 13786 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 literal 0 HcmV?d00001 diff --git a/docs/example/anchors.py b/docs/example/anchors.py new file mode 100644 index 000000000..72361cefc --- /dev/null +++ b/docs/example/anchors.py @@ -0,0 +1,29 @@ +from PIL import Image, ImageDraw, ImageFont + +font = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 16) + + +def test(anchor): + im = Image.new("RGBA", (200, 100), "white") + d = ImageDraw.Draw(im) + d.line(((100, 0), (100, 100)), "gray") + d.line(((0, 50), (200, 50)), "gray") + d.text((100, 50), "Sample", "black", font, anchor) + d.text((10, 100), "anchor=%s" % anchor, "gray", font, "ld") + return im + + +if __name__ == "__main__": + im = Image.new("RGBA", (600, 300), "white") + d = ImageDraw.Draw(im) + for y, row in enumerate( + (("ma", "mt", "mm"), ("ms", "mb", "md"), ("ls", "ms", "rs")) + ): + for x, anchor in enumerate(row): + im.paste(test(anchor), (x * 200, y * 100)) + if x != 0: + d.line(((x * 200, y * 100), (x * 200, (y + 1) * 100)), "black", 3) + if y != 0: + d.line(((x * 200, y * 100), ((x + 1) * 200, y * 100)), "black", 3) + im.save("docs/example/anchors.png") + im.show() diff --git a/docs/handbook/appendices.rst b/docs/handbook/appendices.rst index e6c415cc6..6afaef071 100644 --- a/docs/handbook/appendices.rst +++ b/docs/handbook/appendices.rst @@ -7,4 +7,5 @@ Appendices :maxdepth: 2 image-file-formats + text-anchors writing-your-own-file-decoder diff --git a/docs/handbook/text-anchors.rst b/docs/handbook/text-anchors.rst new file mode 100644 index 000000000..e62210edd --- /dev/null +++ b/docs/handbook/text-anchors.rst @@ -0,0 +1,140 @@ + +.. _text-anchors: + +Text anchors +============ + +The ``anchor`` parameter determines alignment of drawn text relative to the ``xy`` parameter. +The default alignment is top left, specifically ``la`` (left--ascender) for horizontal text +and ``lt`` (left--top) for vertical text. + +This parameter is only supported by OpenType/TrueType fonts. +Other fonts may ignore the parameter and use the default (top left) alignment. + +Specifying an anchor +^^^^^^^^^^^^^^^^^^^^ + +An anchor is specified with a two character string. The first character is the +horizontal alignment, the second character is the vertical alignment. +For example, the default value of ``la`` for horizontal text means left--ascender +aligned text. + +When drawing text with :py:meth:`PIL.ImageDraw.Draw.text` with a specific anchor, +text will be placed such that the specified anchor point is at the ``xy`` coordinates. + +For example, in the following image, text is ``ms`` (middle--baseline) aligned, with ``xy`` at +the intersection of the two lines: + +.. image:: ../../Tests/images/test_anchor_quick_ms.png + :alt: Middle--baseline aligned text. + :align: left + +.. code-block:: python + + from PIL import Image, ImageDraw, ImageFont + + font = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48) + im = Image.new("RGB", (200, 200), "white") + d = ImageDraw.Draw(im) + d.line(((0, 100), (200, 100)), "gray") + d.line(((100, 0), (100, 200)), "gray") + d.text((100, 100), "Quick", fill="black", anchor="ms", font=font) + +.. container:: clearer + + | + +.. only: comment + The container above prevents the image alignment from affecting following text. + +Quick reference +^^^^^^^^^^^^^^^ + +.. image:: ../resources/anchor_horizontal.svg + :alt: Horizontal text + :align: center + +.. image:: ../resources/anchor_vertical.svg + :alt: Vertical text + :align: center + +Horizontal anchor alignment +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +``l`` --- left + Anchor is to the left of the text. + + For *horizontal* text this is the origin of the first glyph, as shown in the `FreeType tutorial`_. + +``m`` --- middle + Anchor is horizontally centered with the text. + + For *vertical* text it is recommended to use ``s`` (baseline) alignment instead, + as it does not change based on the specific glyphs of the given text. + +``r`` --- right + Anchor is to the right of the text. + + For *horizontal* text this is the advanced origin of the last glyph, as shown in the `FreeType tutorial`_. + +``s`` --- baseline *(vertical text only)* + Anchor is at the baseline (middle) of the text. The exact alignment depends on the font. + + For *vertical* text this is the recommended alignment, + as it does not change based on the specific glyphs of the given text + (see image for vertical text above). + +Vertical anchor alignment +^^^^^^^^^^^^^^^^^^^^^^^^^ + +``a`` --- ascender / top *(horizontal text only)* + Anchor is at the ascender line (top) of the first line of text, as defined by the font. + + See `Font metrics on Wikipedia`_ for more information. + +``t`` --- top *(single-line text only)* + Anchor is at the top of the text. + + For *vertical* text this is the origin of the first glyph, as shown in the `FreeType tutorial`_. + + For *horizontal* text it is recommended to use ``a`` (ascender) alignment instead, + as it does not change based on the specific glyphs of the given 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. + + See `Font metrics on Wikipedia`_ for more information. + +``b`` --- bottom *(single-line text only)* + Anchor is at the bottom of the text. + + For *vertical* text this is the advanced origin of the last glyph, as shown in the `FreeType tutorial`_. + + For *horizontal* text it is recommended to use ``d`` (descender) alignment instead, + as it does not change based on the specific glyphs of the given text. + +``d`` --- descender / bottom *(horizontal text only)* + Anchor is at the descender line (bottom) of the last line of text, as defined by the font. + + See `Font metrics on Wikipedia`_ for more information. + +Examples +^^^^^^^^ + +The following image shows several examples of anchors for horizontal text. +In each section the ``xy`` parameter was set to the center shown by the intersection +of the two lines. + +.. comment: Image generated with ../example/anchors.py + +.. image:: ../example/anchors.png + :alt: Text anchor examples + :align: center + +.. _Font metrics on Wikipedia: https://en.wikipedia.org/wiki/Typeface#Font_metrics +.. _FreeType tutorial: https://freetype.org/freetype2/docs/tutorial/step2.html diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index b6c64ac97..9fca33a01 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -70,64 +70,10 @@ contains font metrics, the latter raster data. To load a bitmap font, use the load functions in the :py:mod:`~PIL.ImageFont` module. -To load a OpenType/TrueType font, use the truetype function in the +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 `_ -for more information on the specific terms used. - Example: Draw Partial Opacity Text ---------------------------------- @@ -340,8 +286,8 @@ Methods :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. + See :ref:`text-anchors` for valid values. This parameter is + ignored for non-TrueType fonts. .. note:: This parameter was present in earlier versions of Pillow, but implemented only in version 7.2.0. @@ -400,8 +346,8 @@ Methods :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. + See :ref:`text-anchors` for valid values. This parameter is + ignored for non-TrueType fonts. .. note:: This parameter was present in earlier versions of Pillow, but implemented only in version 7.2.0. diff --git a/docs/resources/anchor_horizontal.svg b/docs/resources/anchor_horizontal.svg new file mode 100644 index 000000000..a0648a10c --- /dev/null +++ b/docs/resources/anchor_horizontal.svg @@ -0,0 +1,467 @@ + + + + + Pillow horizontal text anchors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Pillow horizontal text anchors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (d) descender + (s) baseline + (a) ascender + (m) middle + (t) top + (b) bottom + (l) left + (r) right + (m) middle + + + Horizontal text + + diff --git a/docs/resources/anchor_vertical.svg b/docs/resources/anchor_vertical.svg new file mode 100644 index 000000000..95da30ffd --- /dev/null +++ b/docs/resources/anchor_vertical.svg @@ -0,0 +1,841 @@ + + + + + Pillow vertical text anchors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Pillow vertical text anchors + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (l)left + (s) baseline + (r)right + (t) top + (m) middle + (b) bottom + (m)middle + (l)left + (s) baseline + (r)right + (t) top + (m) middle + (b) bottom + (m)middle + + + Verticaltext + + diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index c9278d0ac..5612270d4 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -396,7 +396,7 @@ class FreeTypeFont: :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. + See :ref:`text-anchors` for valid values. .. versionadded:: 7.2.0 @@ -475,7 +475,7 @@ class FreeTypeFont: :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. + See :ref:`text-anchors` for valid values. .. versionadded:: 7.2.0