使用文本作为路径

使用文本作为路径#

TextPath 创建 Path,该路径是文本字符的轮廓。生成的路径可以例如用作图像的剪切路径。

import matplotlib.pyplot as plt
import numpy as np

from matplotlib.cbook import get_sample_data
from matplotlib.image import BboxImage
from matplotlib.offsetbox import (AnchoredOffsetbox, AnnotationBbox,
                                  AuxTransformBox)
from matplotlib.patches import PathPatch, Shadow
from matplotlib.text import TextPath
from matplotlib.transforms import IdentityTransform


class PathClippedImagePatch(PathPatch):
    """
    The given image is used to draw the face of the patch. Internally,
    it uses BboxImage whose clippath set to the path of the patch.

    FIXME : The result is currently dpi dependent.
    """

    def __init__(self, path, bbox_image, **kwargs):
        super().__init__(path, **kwargs)
        self.bbox_image = BboxImage(
            self.get_window_extent, norm=None, origin=None)
        self.bbox_image.set_data(bbox_image)

    def set_facecolor(self, color):
        """Simply ignore facecolor."""
        super().set_facecolor("none")

    def draw(self, renderer=None):
        # the clip path must be updated every draw. any solution? -JJ
        self.bbox_image.set_clip_path(self._path, self.get_transform())
        self.bbox_image.draw(renderer)
        super().draw(renderer)


if __name__ == "__main__":

    fig, (ax1, ax2) = plt.subplots(2)

    # EXAMPLE 1

    arr = plt.imread(get_sample_data("grace_hopper.jpg"))

    text_path = TextPath((0, 0), "!?", size=150)
    p = PathClippedImagePatch(text_path, arr, ec="k")

    # make offset box
    offsetbox = AuxTransformBox(IdentityTransform())
    offsetbox.add_artist(p)

    # make anchored offset box
    ao = AnchoredOffsetbox(loc='upper left', child=offsetbox, frameon=True,
                           borderpad=0.2)
    ax1.add_artist(ao)

    # another text
    for usetex, ypos, string in [
            (False, 0.25, r"textpath supports mathtext"),
            (True, 0.05, r"textpath supports \TeX"),
    ]:
        text_path = TextPath((0, 0), string, size=20, usetex=usetex)

        p1 = PathPatch(text_path, ec="w", lw=3, fc="w", alpha=0.9)
        p2 = PathPatch(text_path, ec="none", fc="k")

        offsetbox2 = AuxTransformBox(IdentityTransform())
        offsetbox2.add_artist(p1)
        offsetbox2.add_artist(p2)

        ab = AnnotationBbox(offsetbox2, (0.95, ypos),
                            xycoords='axes fraction',
                            boxcoords="offset points",
                            box_alignment=(1., 0.),
                            frameon=False,
                            )
        ax1.add_artist(ab)

    ax1.imshow([[0, 1, 2], [1, 2, 3]], cmap=plt.cm.gist_gray_r,
               interpolation="bilinear", aspect="auto")

    # EXAMPLE 2

    arr = np.arange(256).reshape(1, 256)

    for usetex, xpos, string in [
            (False, 0.25,
             r"$\left[\sum_{n=1}^\infty\frac{-e^{i\pi}}{2^n}\right]$!"),
            (True, 0.75,
             r"$\displaystyle\left[\sum_{n=1}^\infty"
             r"\frac{-e^{i\pi}}{2^n}\right]$!"),
    ]:
        text_path = TextPath((0, 0), string, size=40, usetex=usetex)
        text_patch = PathClippedImagePatch(text_path, arr, ec="none")
        shadow1 = Shadow(text_patch, 1, -1, fc="none", ec="0.6", lw=3)
        shadow2 = Shadow(text_patch, 1, -1, fc="0.3", ec="none")

        # make offset box
        offsetbox = AuxTransformBox(IdentityTransform())
        offsetbox.add_artist(shadow1)
        offsetbox.add_artist(shadow2)
        offsetbox.add_artist(text_patch)

        # place the anchored offset box using AnnotationBbox
        ab = AnnotationBbox(offsetbox, (xpos, 0.5), box_alignment=(0.5, 0.5))

        ax2.add_artist(ab)

    ax2.set_xlim(0, 1)
    ax2.set_ylim(0, 1)

    plt.show()
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Cell In[1], line 63
     58 # another text
     59 for usetex, ypos, string in [
     60         (False, 0.25, r"textpath supports mathtext"),
     61         (True, 0.05, r"textpath supports \TeX"),
     62 ]:
---> 63     text_path = TextPath((0, 0), string, size=20, usetex=usetex)
     65     p1 = PathPatch(text_path, ec="w", lw=3, fc="w", alpha=0.9)
     66     p2 = PathPatch(text_path, ec="none", fc="k")

File /opt/hostedtoolcache/Python/3.12.8/x64/lib/python3.12/site-packages/matplotlib/textpath.py:355, in TextPath.__init__(self, xy, s, size, prop, _interpolation_steps, usetex)
    352 self._cached_vertices = None
    353 s, ismath = Text(usetex=usetex)._preprocess_math(s)
    354 super().__init__(
--> 355     *text_to_path.get_text_path(prop, s, ismath=ismath),
    356     _interpolation_steps=_interpolation_steps,
    357     readonly=True)
    358 self._should_simplify = False

File /opt/hostedtoolcache/Python/3.12.8/x64/lib/python3.12/site-packages/matplotlib/textpath.py:109, in TextToPath.get_text_path(self, prop, s, ismath)
     73 """
     74 Convert text *s* to path (a tuple of vertices and codes for
     75 matplotlib.path.Path).
   (...)
    106 Also see `TextPath` for a more direct way to create a path from a text.
    107 """
    108 if ismath == "TeX":
--> 109     glyph_info, glyph_map, rects = self.get_glyphs_tex(prop, s)
    110 elif not ismath:
    111     font = self._get_font(prop)

File /opt/hostedtoolcache/Python/3.12.8/x64/lib/python3.12/site-packages/matplotlib/textpath.py:219, in TextToPath.get_glyphs_tex(self, prop, s, glyph_map, return_new_glyphs_only)
    216 """Convert the string *s* to vertices and codes using usetex mode."""
    217 # Mostly borrowed from pdf backend.
--> 219 dvifile = TexManager().make_dvi(s, self.FONT_SCALE)
    220 with dviread.Dvi(dvifile, self.DPI) as dvi:
    221     page, = dvi

File /opt/hostedtoolcache/Python/3.12.8/x64/lib/python3.12/site-packages/matplotlib/texmanager.py:292, in TexManager.make_dvi(cls, tex, fontsize)
    290     with TemporaryDirectory(dir=cwd) as tmpdir:
    291         tmppath = Path(tmpdir)
--> 292         cls._run_checked_subprocess(
    293             ["latex", "-interaction=nonstopmode", "--halt-on-error",
    294              f"--output-directory={tmppath.name}",
    295              f"{texfile.name}"], tex, cwd=cwd)
    296         (tmppath / Path(dvifile).name).replace(dvifile)
    297 return dvifile

File /opt/hostedtoolcache/Python/3.12.8/x64/lib/python3.12/site-packages/matplotlib/texmanager.py:255, in TexManager._run_checked_subprocess(cls, command, tex, cwd)
    251     raise RuntimeError(
    252         f'Failed to process string with tex because {command[0]} '
    253         'could not be found') from exc
    254 except subprocess.CalledProcessError as exc:
--> 255     raise RuntimeError(
    256         '{prog} was not able to process the following string:\n'
    257         '{tex!r}\n\n'
    258         'Here is the full command invocation and its output:\n\n'
    259         '{format_command}\n\n'
    260         '{exc}\n\n'.format(
    261             prog=command[0],
    262             format_command=cbook._pformat_subprocess(command),
    263             tex=tex.encode('unicode_escape'),
    264             exc=exc.output.decode('utf-8', 'backslashreplace'))
    265         ) from None
    266 _log.debug(report)
    267 return report

RuntimeError: latex was not able to process the following string:
b'textpath supports \\\\TeX'

Here is the full command invocation and its output:

latex -interaction=nonstopmode --halt-on-error --output-directory=tmppgh67grx 152ce37b8c915b815a2d120ebb44ecad.tex

This is pdfTeX, Version 3.141592653-2.6-1.40.26 (TeX Live 2024) (preloaded format=latex)
 restricted \write18 enabled.
entering extended mode
(./152ce37b8c915b815a2d120ebb44ecad.tex
LaTeX2e <2024-11-01> patch level 1
L3 programming layer <2024-12-09>
(/home/runner/.TinyTeX/texmf-dist/tex/latex/base/article.cls
Document Class: article 2024/06/29 v1.4n Standard LaTeX document class
(/home/runner/.TinyTeX/texmf-dist/tex/latex/base/size10.clo))

! LaTeX Error: File `type1cm.sty' not found.

Type X to quit or <RETURN> to proceed,
or enter new name. (Default extension: sty)

Enter file name: 
! Emergency stop.
<read *> 
         
l.7 \usepackage
               {type1ec}^^M
No pages of output.
Transcript written on tmppgh67grx/152ce37b8c915b815a2d120ebb44ecad.log.
../../_images/3bede04fd32417a465d2ff3ef8fff8f866241e222fb5ac91cb1efe8a8cbc7629.png