Pillow/src/PIL/ImageFont.py
2023-03-12 12:39:45 +02:00

1207 lines
49 KiB
Python

#
# The Python Imaging Library.
# $Id$
#
# PIL raster font management
#
# History:
# 1996-08-07 fl created (experimental)
# 1997-08-25 fl minor adjustments to handle fonts from pilfont 0.3
# 1999-02-06 fl rewrote most font management stuff in C
# 1999-03-17 fl take pth files into account in load_path (from Richard Jones)
# 2001-02-17 fl added freetype support
# 2001-05-09 fl added TransposedFont wrapper class
# 2002-03-04 fl make sure we have a "L" or "1" font
# 2002-12-04 fl skip non-directory entries in the system path
# 2003-04-29 fl add embedded default font
# 2003-09-27 fl added support for truetype charmap encodings
#
# Todo:
# Adapt to PILFONT2 format (16-bit fonts, compressed, single file)
#
# Copyright (c) 1997-2003 by Secret Labs AB
# Copyright (c) 1996-2003 by Fredrik Lundh
#
# See the README file for information on usage and redistribution.
#
import base64
import math
import os
import sys
import warnings
from enum import IntEnum
from io import BytesIO
from . import Image
from ._deprecate import deprecate
from ._util import is_directory, is_path
class Layout(IntEnum):
BASIC = 0
RAQM = 1
def __getattr__(name):
for enum, prefix in {Layout: "LAYOUT_"}.items():
if name.startswith(prefix):
name = name[len(prefix) :]
if name in enum.__members__:
deprecate(f"{prefix}{name}", 10, f"{enum.__name__}.{name}")
return enum[name]
msg = f"module '{__name__}' has no attribute '{name}'"
raise AttributeError(msg)
class _ImagingFtNotInstalled:
# module placeholder
def __getattr__(self, id):
msg = "The _imagingft C module is not installed"
raise ImportError(msg)
try:
from . import _imagingft as core
except ImportError:
core = _ImagingFtNotInstalled()
_UNSPECIFIED = object()
# FIXME: add support for pilfont2 format (see FontFile.py)
# --------------------------------------------------------------------
# Font metrics format:
# "PILfont" LF
# fontdescriptor LF
# (optional) key=value... LF
# "DATA" LF
# binary data: 256*10*2 bytes (dx, dy, dstbox, srcbox)
#
# To place a character, cut out srcbox and paste at dstbox,
# relative to the character position. Then move the character
# position according to dx, dy.
# --------------------------------------------------------------------
class ImageFont:
"""PIL font wrapper"""
def _load_pilfont(self, filename):
with open(filename, "rb") as fp:
image = None
for ext in (".png", ".gif", ".pbm"):
if image:
image.close()
try:
fullname = os.path.splitext(filename)[0] + ext
image = Image.open(fullname)
except Exception:
pass
else:
if image and image.mode in ("1", "L"):
break
else:
if image:
image.close()
msg = "cannot find glyph data file"
raise OSError(msg)
self.file = fullname
self._load_pilfont_data(fp, image)
image.close()
def _load_pilfont_data(self, file, image):
# read PILfont header
if file.readline() != b"PILfont\n":
msg = "Not a PILfont file"
raise SyntaxError(msg)
file.readline().split(b";")
self.info = [] # FIXME: should be a dictionary
while True:
s = file.readline()
if not s or s == b"DATA\n":
break
self.info.append(s)
# read PILfont metrics
data = file.read(256 * 20)
# check image
if image.mode not in ("1", "L"):
msg = "invalid font image mode"
raise TypeError(msg)
image.load()
self.font = Image.core.font(image.im, data)
def getsize(self, text, *args, **kwargs):
"""
.. deprecated:: 9.2.0
Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead.
See :ref:`deprecations <Font size and offset methods>` for more information.
Returns width and height (in pixels) of given text.
:param text: Text to measure.
:return: (width, height)
"""
deprecate("getsize", 10, "getbbox or getlength")
return self.font.getsize(text)
def getmask(self, text, mode="", *args, **kwargs):
"""
Create a bitmap for the text.
If the font uses antialiasing, the bitmap should have mode ``L`` and use a
maximum value of 255. Otherwise, it should have mode ``1``.
:param text: Text to render.
:param mode: Used by some graphics drivers to indicate what mode the
driver prefers; if empty, the renderer may return either
mode. Note that the mode is always a string, to simplify
C-level implementations.
.. versionadded:: 1.1.5
:return: An internal PIL storage memory instance as defined by the
:py:mod:`PIL.Image.core` interface module.
"""
return self.font.getmask(text, mode)
def getbbox(self, text, *args, **kwargs):
"""
Returns bounding box (in pixels) of given text.
.. versionadded:: 9.2.0
:param text: Text to render.
:param mode: Used by some graphics drivers to indicate what mode the
driver prefers; if empty, the renderer may return either
mode. Note that the mode is always a string, to simplify
C-level implementations.
:return: ``(left, top, right, bottom)`` bounding box
"""
width, height = self.font.getsize(text)
return 0, 0, width, height
def getlength(self, text, *args, **kwargs):
"""
Returns length (in pixels) of given text.
This is the amount by which following text should be offset.
.. versionadded:: 9.2.0
"""
width, height = self.font.getsize(text)
return width
##
# Wrapper for FreeType fonts. Application code should use the
# <b>truetype</b> factory function to create font objects.
class FreeTypeFont:
"""FreeType font wrapper (requires _imagingft service)"""
def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None):
# FIXME: use service provider instead
self.path = font
self.size = size
self.index = index
self.encoding = encoding
if layout_engine not in (Layout.BASIC, Layout.RAQM):
layout_engine = Layout.BASIC
if core.HAVE_RAQM:
layout_engine = Layout.RAQM
elif layout_engine == Layout.RAQM and not core.HAVE_RAQM:
warnings.warn(
"Raqm layout was requested, but Raqm is not available. "
"Falling back to basic layout."
)
layout_engine = Layout.BASIC
self.layout_engine = layout_engine
def load_from_bytes(f):
self.font_bytes = f.read()
self.font = core.getfont(
"", size, index, encoding, self.font_bytes, layout_engine
)
if is_path(font):
if sys.platform == "win32":
font_bytes_path = font if isinstance(font, bytes) else font.encode()
try:
font_bytes_path.decode("ascii")
except UnicodeDecodeError:
# FreeType cannot load fonts with non-ASCII characters on Windows
# So load it into memory first
with open(font, "rb") as f:
load_from_bytes(f)
return
self.font = core.getfont(
font, size, index, encoding, layout_engine=layout_engine
)
else:
load_from_bytes(font)
def __getstate__(self):
return [self.path, self.size, self.index, self.encoding, self.layout_engine]
def __setstate__(self, state):
path, size, index, encoding, layout_engine = state
self.__init__(path, size, index, encoding, layout_engine)
def _multiline_split(self, text):
split_character = "\n" if isinstance(text, str) else b"\n"
return text.split(split_character)
def getname(self):
"""
:return: A tuple of the font family (e.g. Helvetica) and the font style
(e.g. Bold)
"""
return self.font.family, self.font.style
def getmetrics(self):
"""
:return: A tuple of the font ascent (the distance from the baseline to
the highest outline point) and descent (the distance from the
baseline to the lowest outline point, a negative value)
"""
return self.font.ascent, self.font.descent
def getlength(self, text, mode="", direction=None, features=None, language=None):
"""
Returns length (in pixels with 1/64 precision) of given text when rendered
in font with provided direction, features, and language.
This is the amount by which following text should be offset.
Text bounding box may extend past the length in some fonts,
e.g. when using italics or accents.
The result is returned as a float; it is a whole number if using basic layout.
Note that the sum of two lengths may not equal the length of a concatenated
string due to kerning. If you need to adjust for kerning, include the following
character and subtract its length.
For example, instead of ::
hello = font.getlength("Hello")
world = font.getlength("World")
hello_world = hello + world # not adjusted for kerning
assert hello_world == font.getlength("HelloWorld") # may fail
use ::
hello = font.getlength("HelloW") - font.getlength("W") # adjusted for kerning
world = font.getlength("World")
hello_world = hello + world # adjusted for kerning
assert hello_world == font.getlength("HelloWorld") # True
or disable kerning with (requires libraqm) ::
hello = draw.textlength("Hello", font, features=["-kern"])
world = draw.textlength("World", font, features=["-kern"])
hello_world = hello + world # kerning is disabled, no need to adjust
assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"])
.. versionadded:: 8.0.0
:param text: Text to measure.
:param mode: Used by some graphics drivers to indicate what mode the
driver prefers; if empty, the renderer may return either
mode. Note that the mode is always a string, to simplify
C-level implementations.
:param direction: Direction of the text. It can be 'rtl' (right to
left), 'ltr' (left to right) or 'ttb' (top to bottom).
Requires libraqm.
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
for example 'dlig' or 'ss01', but can be also
used to turn off default font features for
example '-liga' to disable ligatures or '-kern'
to disable kerning. To get all supported
features, see
https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
Requires libraqm.
:param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`_
Requires libraqm.
:return: Width for horizontal, height for vertical text.
"""
return self.font.getlength(text, mode, direction, features, language) / 64
def getbbox(
self,
text,
mode="",
direction=None,
features=None,
language=None,
stroke_width=0,
anchor=None,
):
"""
Returns bounding box (in pixels) of given text relative to given anchor
when rendered in font with provided direction, features, and language.
Use :py:meth:`getlength()` to get the offset of following text with
1/64 pixel precision. The bounding box includes extra margins for
some fonts, e.g. italics or accents.
.. versionadded:: 8.0.0
:param text: Text to render.
:param mode: Used by some graphics drivers to indicate what mode the
driver prefers; if empty, the renderer may return either
mode. Note that the mode is always a string, to simplify
C-level implementations.
:param direction: Direction of the text. It can be 'rtl' (right to
left), 'ltr' (left to right) or 'ttb' (top to bottom).
Requires libraqm.
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
for example 'dlig' or 'ss01', but can be also
used to turn off default font features for
example '-liga' to disable ligatures or '-kern'
to disable kerning. To get all supported
features, see
https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
Requires libraqm.
:param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`_
Requires libraqm.
:param stroke_width: The width of the text stroke.
: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.
:return: ``(left, top, right, bottom)`` bounding box
"""
size, offset = self.font.getsize(
text, mode, direction, features, language, anchor
)
left, top = offset[0] - stroke_width, offset[1] - stroke_width
width, height = size[0] + 2 * stroke_width, size[1] + 2 * stroke_width
return left, top, left + width, top + height
def getsize(
self,
text,
direction=None,
features=None,
language=None,
stroke_width=0,
):
"""
.. deprecated:: 9.2.0
Use :py:meth:`getlength()` to measure the offset of following text with
1/64 pixel precision.
Use :py:meth:`getbbox()` to get the exact bounding box based on an anchor.
See :ref:`deprecations <Font size and offset methods>` for more information.
Returns width and height (in pixels) of given text if rendered in font with
provided direction, features, and language.
.. note:: For historical reasons this function measures text height from
the ascender line instead of the top, see :ref:`text-anchors`.
If you wish to measure text height from the top, it is recommended
to use the bottom value of :meth:`getbbox` with ``anchor='lt'`` instead.
:param text: Text to measure.
:param direction: Direction of the text. It can be 'rtl' (right to
left), 'ltr' (left to right) or 'ttb' (top to bottom).
Requires libraqm.
.. versionadded:: 4.2.0
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
for example 'dlig' or 'ss01', but can be also
used to turn off default font features for
example '-liga' to disable ligatures or '-kern'
to disable kerning. To get all supported
features, see
https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
Requires libraqm.
.. versionadded:: 4.2.0
:param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`_
Requires libraqm.
.. versionadded:: 6.0.0
:param stroke_width: The width of the text stroke.
.. versionadded:: 6.2.0
:return: (width, height)
"""
deprecate("getsize", 10, "getbbox or getlength")
# vertical offset is added for historical reasons
# see https://github.com/python-pillow/Pillow/pull/4910#discussion_r486682929
size, offset = self.font.getsize(text, "L", direction, features, language)
return (
size[0] + stroke_width * 2,
size[1] + stroke_width * 2 + offset[1],
)
def getsize_multiline(
self,
text,
direction=None,
spacing=4,
features=None,
language=None,
stroke_width=0,
):
"""
.. deprecated:: 9.2.0
Use :py:meth:`.ImageDraw.multiline_textbbox` instead.
See :ref:`deprecations <Font size and offset methods>` for more information.
Returns width and height (in pixels) of given text if rendered in font
with provided direction, features, and language, while respecting
newline characters.
:param text: Text to measure.
:param direction: Direction of the text. It can be 'rtl' (right to
left), 'ltr' (left to right) or 'ttb' (top to bottom).
Requires libraqm.
:param spacing: The vertical gap between lines, defaulting to 4 pixels.
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
for example 'dlig' or 'ss01', but can be also
used to turn off default font features for
example '-liga' to disable ligatures or '-kern'
to disable kerning. To get all supported
features, see
https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
Requires libraqm.
:param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`_
Requires libraqm.
.. versionadded:: 6.0.0
:param stroke_width: The width of the text stroke.
.. versionadded:: 6.2.0
:return: (width, height)
"""
deprecate("getsize_multiline", 10, "ImageDraw.multiline_textbbox")
max_width = 0
lines = self._multiline_split(text)
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
line_spacing = self.getsize("A", stroke_width=stroke_width)[1] + spacing
for line in lines:
line_width, line_height = self.getsize(
line, direction, features, language, stroke_width
)
max_width = max(max_width, line_width)
return max_width, len(lines) * line_spacing - spacing
def getoffset(self, text):
"""
.. deprecated:: 9.2.0
Use :py:meth:`.getbbox` instead.
See :ref:`deprecations <Font size and offset methods>` for more information.
Returns the offset of given text. This is the gap between the
starting coordinate and the first marking. Note that this gap is
included in the result of :py:func:`~PIL.ImageFont.FreeTypeFont.getsize`.
:param text: Text to measure.
:return: A tuple of the x and y offset
"""
deprecate("getoffset", 10, "getbbox")
return self.font.getsize(text)[1]
def getmask(
self,
text,
mode="",
direction=None,
features=None,
language=None,
stroke_width=0,
anchor=None,
ink=0,
start=None,
):
"""
Create a bitmap for the text.
If the font uses antialiasing, the bitmap should have mode ``L`` and use a
maximum value of 255. If the font has embedded color data, the bitmap
should have mode ``RGBA``. Otherwise, it should have mode ``1``.
:param text: Text to render.
:param mode: Used by some graphics drivers to indicate what mode the
driver prefers; if empty, the renderer may return either
mode. Note that the mode is always a string, to simplify
C-level implementations.
.. versionadded:: 1.1.5
:param direction: Direction of the text. It can be 'rtl' (right to
left), 'ltr' (left to right) or 'ttb' (top to bottom).
Requires libraqm.
.. versionadded:: 4.2.0
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
for example 'dlig' or 'ss01', but can be also
used to turn off default font features for
example '-liga' to disable ligatures or '-kern'
to disable kerning. To get all supported
features, see
https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
Requires libraqm.
.. versionadded:: 4.2.0
:param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`_
Requires libraqm.
.. versionadded:: 6.0.0
:param stroke_width: The width of the text stroke.
.. 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
:param ink: Foreground ink for rendering in RGBA mode.
.. versionadded:: 8.0.0
:param start: Tuple of horizontal and vertical offset, as text may render
differently when starting at fractional coordinates.
.. versionadded:: 9.4.0
:return: An internal PIL storage memory instance as defined by the
:py:mod:`PIL.Image.core` interface module.
"""
return self.getmask2(
text,
mode,
direction=direction,
features=features,
language=language,
stroke_width=stroke_width,
anchor=anchor,
ink=ink,
start=start,
)[0]
def getmask2(
self,
text,
mode="",
fill=_UNSPECIFIED,
direction=None,
features=None,
language=None,
stroke_width=0,
anchor=None,
ink=0,
start=None,
*args,
**kwargs,
):
"""
Create a bitmap for the text.
If the font uses antialiasing, the bitmap should have mode ``L`` and use a
maximum value of 255. If the font has embedded color data, the bitmap
should have mode ``RGBA``. Otherwise, it should have mode ``1``.
:param text: Text to render.
:param mode: Used by some graphics drivers to indicate what mode the
driver prefers; if empty, the renderer may return either
mode. Note that the mode is always a string, to simplify
C-level implementations.
.. versionadded:: 1.1.5
:param fill: Optional fill function. By default, an internal Pillow function
will be used.
Deprecated. This parameter will be removed in Pillow 10
(2023-07-01).
:param direction: Direction of the text. It can be 'rtl' (right to
left), 'ltr' (left to right) or 'ttb' (top to bottom).
Requires libraqm.
.. versionadded:: 4.2.0
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
for example 'dlig' or 'ss01', but can be also
used to turn off default font features for
example '-liga' to disable ligatures or '-kern'
to disable kerning. To get all supported
features, see
https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
Requires libraqm.
.. versionadded:: 4.2.0
:param language: Language of the text. Different languages may use
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`_
Requires libraqm.
.. versionadded:: 6.0.0
:param stroke_width: The width of the text stroke.
.. 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
:param ink: Foreground ink for rendering in RGBA mode.
.. versionadded:: 8.0.0
:param start: Tuple of horizontal and vertical offset, as text may render
differently when starting at fractional coordinates.
.. versionadded:: 9.4.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
"""
if fill is _UNSPECIFIED:
fill = Image.core.fill
else:
deprecate("fill", 10)
size, offset = self.font.getsize(
text, mode, direction, features, language, anchor
)
if start is None:
start = (0, 0)
size = tuple(math.ceil(size[i] + stroke_width * 2 + start[i]) for i in range(2))
offset = offset[0] - stroke_width, offset[1] - stroke_width
Image._decompression_bomb_check(size)
im = fill("RGBA" if mode == "RGBA" else "L", size, 0)
self.font.render(
text,
im.id,
mode,
direction,
features,
language,
stroke_width,
ink,
start[0],
start[1],
)
return im, offset
def font_variant(
self, font=None, size=None, index=None, encoding=None, layout_engine=None
):
"""
Create a copy of this FreeTypeFont object,
using any specified arguments to override the settings.
Parameters are identical to the parameters used to initialize this
object.
:return: A FreeTypeFont object.
"""
if font is None:
try:
font = BytesIO(self.font_bytes)
except AttributeError:
font = self.path
return FreeTypeFont(
font=font,
size=self.size if size is None else size,
index=self.index if index is None else index,
encoding=self.encoding if encoding is None else encoding,
layout_engine=layout_engine or self.layout_engine,
)
def get_variation_names(self):
"""
:returns: A list of the named styles in a variation font.
:exception OSError: If the font is not a variation font.
"""
try:
names = self.font.getvarnames()
except AttributeError as e:
msg = "FreeType 2.9.1 or greater is required"
raise NotImplementedError(msg) from e
return [name.replace(b"\x00", b"") for name in names]
def set_variation_by_name(self, name):
"""
:param name: The name of the style.
:exception OSError: If the font is not a variation font.
"""
names = self.get_variation_names()
if not isinstance(name, bytes):
name = name.encode()
index = names.index(name) + 1
if index == getattr(self, "_last_variation_index", None):
# When the same name is set twice in a row,
# there is an 'unknown freetype error'
# https://savannah.nongnu.org/bugs/?56186
return
self._last_variation_index = index
self.font.setvarname(index)
def get_variation_axes(self):
"""
:returns: A list of the axes in a variation font.
:exception OSError: If the font is not a variation font.
"""
try:
axes = self.font.getvaraxes()
except AttributeError as e:
msg = "FreeType 2.9.1 or greater is required"
raise NotImplementedError(msg) from e
for axis in axes:
axis["name"] = axis["name"].replace(b"\x00", b"")
return axes
def set_variation_by_axes(self, axes):
"""
:param axes: A list of values for each axis.
:exception OSError: If the font is not a variation font.
"""
try:
self.font.setvaraxes(axes)
except AttributeError as e:
msg = "FreeType 2.9.1 or greater is required"
raise NotImplementedError(msg) from e
class TransposedFont:
"""Wrapper for writing rotated or mirrored text"""
def __init__(self, font, orientation=None):
"""
Wrapper that creates a transposed font from any existing font
object.
:param font: A font object.
:param orientation: An optional orientation. If given, this should
be one of Image.Transpose.FLIP_LEFT_RIGHT, Image.Transpose.FLIP_TOP_BOTTOM,
Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_180, or
Image.Transpose.ROTATE_270.
"""
self.font = font
self.orientation = orientation # any 'transpose' argument, or None
def getsize(self, text, *args, **kwargs):
"""
.. deprecated:: 9.2.0
Use :py:meth:`.getbbox` or :py:meth:`.getlength` instead.
See :ref:`deprecations <Font size and offset methods>` for more information.
"""
deprecate("getsize", 10, "getbbox or getlength")
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
w, h = self.font.getsize(text)
if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
return h, w
return w, h
def getmask(self, text, mode="", *args, **kwargs):
im = self.font.getmask(text, mode, *args, **kwargs)
if self.orientation is not None:
return im.transpose(self.orientation)
return im
def getbbox(self, text, *args, **kwargs):
# TransposedFont doesn't support getmask2, move top-left point to (0, 0)
# this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont
left, top, right, bottom = self.font.getbbox(text, *args, **kwargs)
width = right - left
height = bottom - top
if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
return 0, 0, height, width
return 0, 0, width, height
def getlength(self, text, *args, **kwargs):
if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
msg = "text length is undefined for text rotated by 90 or 270 degrees"
raise ValueError(msg)
return self.font.getlength(text, *args, **kwargs)
def load(filename):
"""
Load a font file. This function loads a font object from the given
bitmap font file, and returns the corresponding font object.
:param filename: Name of font file.
:return: A font object.
:exception OSError: If the file could not be read.
"""
f = ImageFont()
f._load_pilfont(filename)
return f
def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
"""
Load a TrueType or OpenType font from a file or file-like object,
and create a font object.
This function loads a font object from the given file or file-like
object, and creates a font object for a font of the given size.
Pillow uses FreeType to open font files. On Windows, be aware that FreeType
will keep the file open as long as the FreeTypeFont object exists. Windows
limits the number of files that can be open in C at once to 512, so if many
fonts are opened simultaneously and that limit is approached, an
``OSError`` may be thrown, reporting that FreeType "cannot open resource".
A workaround would be to copy the file(s) into memory, and open that instead.
This function requires the _imagingft service.
:param font: A filename or file-like object containing a TrueType font.
If the file is not found in this filename, the loader may also
search in other directories, such as the :file:`fonts/`
directory on Windows or :file:`/Library/Fonts/`,
:file:`/System/Library/Fonts/` and :file:`~/Library/Fonts/` on
macOS.
:param size: The requested size, in pixels.
:param index: Which font face to load (default is first available face).
:param encoding: Which font encoding to use (default is Unicode). Possible
encodings include (see the FreeType documentation for more
information):
* "unic" (Unicode)
* "symb" (Microsoft Symbol)
* "ADOB" (Adobe Standard)
* "ADBE" (Adobe Expert)
* "ADBC" (Adobe Custom)
* "armn" (Apple Roman)
* "sjis" (Shift JIS)
* "gb " (PRC)
* "big5"
* "wans" (Extended Wansung)
* "joha" (Johab)
* "lat1" (Latin-1)
This specifies the character set to use. It does not alter the
encoding of any text provided in subsequent operations.
:param layout_engine: Which layout engine to use, if available:
:data:`.ImageFont.Layout.BASIC` or :data:`.ImageFont.Layout.RAQM`.
If it is available, Raqm layout will be used by default.
Otherwise, basic layout will be used.
Raqm layout is recommended for all non-English text. If Raqm layout
is not required, basic layout will have better performance.
You can check support for Raqm layout using
:py:func:`PIL.features.check_feature` with ``feature="raqm"``.
.. versionadded:: 4.2.0
:return: A font object.
:exception OSError: If the file could not be read.
"""
def freetype(font):
return FreeTypeFont(font, size, index, encoding, layout_engine)
try:
return freetype(font)
except OSError:
if not is_path(font):
raise
ttf_filename = os.path.basename(font)
dirs = []
if sys.platform == "win32":
# check the windows font repository
# NOTE: must use uppercase WINDIR, to work around bugs in
# 1.5.2's os.environ.get()
windir = os.environ.get("WINDIR")
if windir:
dirs.append(os.path.join(windir, "fonts"))
elif sys.platform in ("linux", "linux2"):
lindirs = os.environ.get("XDG_DATA_DIRS")
if not lindirs:
# According to the freedesktop spec, XDG_DATA_DIRS should
# default to /usr/share
lindirs = "/usr/share"
dirs += [os.path.join(lindir, "fonts") for lindir in lindirs.split(":")]
elif sys.platform == "darwin":
dirs += [
"/Library/Fonts",
"/System/Library/Fonts",
os.path.expanduser("~/Library/Fonts"),
]
ext = os.path.splitext(ttf_filename)[1]
first_font_with_a_different_extension = None
for directory in dirs:
for walkroot, walkdir, walkfilenames in os.walk(directory):
for walkfilename in walkfilenames:
if ext and walkfilename == ttf_filename:
return freetype(os.path.join(walkroot, walkfilename))
elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
fontpath = os.path.join(walkroot, walkfilename)
if os.path.splitext(fontpath)[1] == ".ttf":
return freetype(fontpath)
if not ext and first_font_with_a_different_extension is None:
first_font_with_a_different_extension = fontpath
if first_font_with_a_different_extension:
return freetype(first_font_with_a_different_extension)
raise
def load_path(filename):
"""
Load font file. Same as :py:func:`~PIL.ImageFont.load`, but searches for a
bitmap font along the Python path.
:param filename: Name of font file.
:return: A font object.
:exception OSError: If the file could not be read.
"""
for directory in sys.path:
if is_directory(directory):
if not isinstance(filename, str):
filename = filename.decode("utf-8")
try:
return load(os.path.join(directory, filename))
except OSError:
pass
msg = "cannot find font file"
raise OSError(msg)
def load_default():
"""Load a "better than nothing" default font.
.. versionadded:: 1.1.4
:return: A font object.
"""
f = ImageFont()
f._load_pilfont_data(
# courB08
BytesIO(
base64.b64decode(
b"""
UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAA//8AAQAAAAAAAAABAAEA
BgAAAAH/+gADAAAAAQAAAAMABgAGAAAAAf/6AAT//QADAAAABgADAAYAAAAA//kABQABAAYAAAAL
AAgABgAAAAD/+AAFAAEACwAAABAACQAGAAAAAP/5AAUAAAAQAAAAFQAHAAYAAP////oABQAAABUA
AAAbAAYABgAAAAH/+QAE//wAGwAAAB4AAwAGAAAAAf/5AAQAAQAeAAAAIQAIAAYAAAAB//kABAAB
ACEAAAAkAAgABgAAAAD/+QAE//0AJAAAACgABAAGAAAAAP/6AAX//wAoAAAALQAFAAYAAAAB//8A
BAACAC0AAAAwAAMABgAAAAD//AAF//0AMAAAADUAAQAGAAAAAf//AAMAAAA1AAAANwABAAYAAAAB
//kABQABADcAAAA7AAgABgAAAAD/+QAFAAAAOwAAAEAABwAGAAAAAP/5AAYAAABAAAAARgAHAAYA
AAAA//kABQAAAEYAAABLAAcABgAAAAD/+QAFAAAASwAAAFAABwAGAAAAAP/5AAYAAABQAAAAVgAH
AAYAAAAA//kABQAAAFYAAABbAAcABgAAAAD/+QAFAAAAWwAAAGAABwAGAAAAAP/5AAUAAABgAAAA
ZQAHAAYAAAAA//kABQAAAGUAAABqAAcABgAAAAD/+QAFAAAAagAAAG8ABwAGAAAAAf/8AAMAAABv
AAAAcQAEAAYAAAAA//wAAwACAHEAAAB0AAYABgAAAAD/+gAE//8AdAAAAHgABQAGAAAAAP/7AAT/
/gB4AAAAfAADAAYAAAAB//oABf//AHwAAACAAAUABgAAAAD/+gAFAAAAgAAAAIUABgAGAAAAAP/5
AAYAAQCFAAAAiwAIAAYAAP////oABgAAAIsAAACSAAYABgAA////+gAFAAAAkgAAAJgABgAGAAAA
AP/6AAUAAACYAAAAnQAGAAYAAP////oABQAAAJ0AAACjAAYABgAA////+gAFAAAAowAAAKkABgAG
AAD////6AAUAAACpAAAArwAGAAYAAAAA//oABQAAAK8AAAC0AAYABgAA////+gAGAAAAtAAAALsA
BgAGAAAAAP/6AAQAAAC7AAAAvwAGAAYAAP////oABQAAAL8AAADFAAYABgAA////+gAGAAAAxQAA
AMwABgAGAAD////6AAUAAADMAAAA0gAGAAYAAP////oABQAAANIAAADYAAYABgAA////+gAGAAAA
2AAAAN8ABgAGAAAAAP/6AAUAAADfAAAA5AAGAAYAAP////oABQAAAOQAAADqAAYABgAAAAD/+gAF
AAEA6gAAAO8ABwAGAAD////6AAYAAADvAAAA9gAGAAYAAAAA//oABQAAAPYAAAD7AAYABgAA////
+gAFAAAA+wAAAQEABgAGAAD////6AAYAAAEBAAABCAAGAAYAAP////oABgAAAQgAAAEPAAYABgAA
////+gAGAAABDwAAARYABgAGAAAAAP/6AAYAAAEWAAABHAAGAAYAAP////oABgAAARwAAAEjAAYA
BgAAAAD/+gAFAAABIwAAASgABgAGAAAAAf/5AAQAAQEoAAABKwAIAAYAAAAA//kABAABASsAAAEv
AAgABgAAAAH/+QAEAAEBLwAAATIACAAGAAAAAP/5AAX//AEyAAABNwADAAYAAAAAAAEABgACATcA
AAE9AAEABgAAAAH/+QAE//wBPQAAAUAAAwAGAAAAAP/7AAYAAAFAAAABRgAFAAYAAP////kABQAA
AUYAAAFMAAcABgAAAAD/+wAFAAABTAAAAVEABQAGAAAAAP/5AAYAAAFRAAABVwAHAAYAAAAA//sA
BQAAAVcAAAFcAAUABgAAAAD/+QAFAAABXAAAAWEABwAGAAAAAP/7AAYAAgFhAAABZwAHAAYAAP//
//kABQAAAWcAAAFtAAcABgAAAAD/+QAGAAABbQAAAXMABwAGAAAAAP/5AAQAAgFzAAABdwAJAAYA
AP////kABgAAAXcAAAF+AAcABgAAAAD/+QAGAAABfgAAAYQABwAGAAD////7AAUAAAGEAAABigAF
AAYAAP////sABQAAAYoAAAGQAAUABgAAAAD/+wAFAAABkAAAAZUABQAGAAD////7AAUAAgGVAAAB
mwAHAAYAAAAA//sABgACAZsAAAGhAAcABgAAAAD/+wAGAAABoQAAAacABQAGAAAAAP/7AAYAAAGn
AAABrQAFAAYAAAAA//kABgAAAa0AAAGzAAcABgAA////+wAGAAABswAAAboABQAGAAD////7AAUA
AAG6AAABwAAFAAYAAP////sABgAAAcAAAAHHAAUABgAAAAD/+wAGAAABxwAAAc0ABQAGAAD////7
AAYAAgHNAAAB1AAHAAYAAAAA//sABQAAAdQAAAHZAAUABgAAAAH/+QAFAAEB2QAAAd0ACAAGAAAA
Av/6AAMAAQHdAAAB3gAHAAYAAAAA//kABAABAd4AAAHiAAgABgAAAAD/+wAF//0B4gAAAecAAgAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAB
//sAAwACAecAAAHpAAcABgAAAAD/+QAFAAEB6QAAAe4ACAAGAAAAAP/5AAYAAAHuAAAB9AAHAAYA
AAAA//oABf//AfQAAAH5AAUABgAAAAD/+QAGAAAB+QAAAf8ABwAGAAAAAv/5AAMAAgH/AAACAAAJ
AAYAAAAA//kABQABAgAAAAIFAAgABgAAAAH/+gAE//sCBQAAAggAAQAGAAAAAP/5AAYAAAIIAAAC
DgAHAAYAAAAB//kABf/+Ag4AAAISAAUABgAA////+wAGAAACEgAAAhkABQAGAAAAAP/7AAX//gIZ
AAACHgADAAYAAAAA//wABf/9Ah4AAAIjAAEABgAAAAD/+QAHAAACIwAAAioABwAGAAAAAP/6AAT/
+wIqAAACLgABAAYAAAAA//kABP/8Ai4AAAIyAAMABgAAAAD/+gAFAAACMgAAAjcABgAGAAAAAf/5
AAT//QI3AAACOgAEAAYAAAAB//kABP/9AjoAAAI9AAQABgAAAAL/+QAE//sCPQAAAj8AAgAGAAD/
///7AAYAAgI/AAACRgAHAAYAAAAA//kABgABAkYAAAJMAAgABgAAAAH//AAD//0CTAAAAk4AAQAG
AAAAAf//AAQAAgJOAAACUQADAAYAAAAB//kABP/9AlEAAAJUAAQABgAAAAH/+QAF//4CVAAAAlgA
BQAGAAD////7AAYAAAJYAAACXwAFAAYAAP////kABgAAAl8AAAJmAAcABgAA////+QAGAAACZgAA
Am0ABwAGAAD////5AAYAAAJtAAACdAAHAAYAAAAA//sABQACAnQAAAJ5AAcABgAA////9wAGAAAC
eQAAAoAACQAGAAD////3AAYAAAKAAAAChwAJAAYAAP////cABgAAAocAAAKOAAkABgAA////9wAG
AAACjgAAApUACQAGAAD////4AAYAAAKVAAACnAAIAAYAAP////cABgAAApwAAAKjAAkABgAA////
+gAGAAACowAAAqoABgAGAAAAAP/6AAUAAgKqAAACrwAIAAYAAP////cABQAAAq8AAAK1AAkABgAA
////9wAFAAACtQAAArsACQAGAAD////3AAUAAAK7AAACwQAJAAYAAP////gABQAAAsEAAALHAAgA
BgAAAAD/9wAEAAACxwAAAssACQAGAAAAAP/3AAQAAALLAAACzwAJAAYAAAAA//cABAAAAs8AAALT
AAkABgAAAAD/+AAEAAAC0wAAAtcACAAGAAD////6AAUAAALXAAAC3QAGAAYAAP////cABgAAAt0A
AALkAAkABgAAAAD/9wAFAAAC5AAAAukACQAGAAAAAP/3AAUAAALpAAAC7gAJAAYAAAAA//cABQAA
Au4AAALzAAkABgAAAAD/9wAFAAAC8wAAAvgACQAGAAAAAP/4AAUAAAL4AAAC/QAIAAYAAAAA//oA
Bf//Av0AAAMCAAUABgAA////+gAGAAADAgAAAwkABgAGAAD////3AAYAAAMJAAADEAAJAAYAAP//
//cABgAAAxAAAAMXAAkABgAA////9wAGAAADFwAAAx4ACQAGAAD////4AAYAAAAAAAoABwASAAYA
AP////cABgAAAAcACgAOABMABgAA////+gAFAAAADgAKABQAEAAGAAD////6AAYAAAAUAAoAGwAQ
AAYAAAAA//gABgAAABsACgAhABIABgAAAAD/+AAGAAAAIQAKACcAEgAGAAAAAP/4AAYAAAAnAAoA
LQASAAYAAAAA//gABgAAAC0ACgAzABIABgAAAAD/+QAGAAAAMwAKADkAEQAGAAAAAP/3AAYAAAA5
AAoAPwATAAYAAP////sABQAAAD8ACgBFAA8ABgAAAAD/+wAFAAIARQAKAEoAEQAGAAAAAP/4AAUA
AABKAAoATwASAAYAAAAA//gABQAAAE8ACgBUABIABgAAAAD/+AAFAAAAVAAKAFkAEgAGAAAAAP/5
AAUAAABZAAoAXgARAAYAAAAA//gABgAAAF4ACgBkABIABgAAAAD/+AAGAAAAZAAKAGoAEgAGAAAA
AP/4AAYAAABqAAoAcAASAAYAAAAA//kABgAAAHAACgB2ABEABgAAAAD/+AAFAAAAdgAKAHsAEgAG
AAD////4AAYAAAB7AAoAggASAAYAAAAA//gABQAAAIIACgCHABIABgAAAAD/+AAFAAAAhwAKAIwA
EgAGAAAAAP/4AAUAAACMAAoAkQASAAYAAAAA//gABQAAAJEACgCWABIABgAAAAD/+QAFAAAAlgAK
AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA
pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
+QAGAAIAzgAKANUAEw==
"""
)
),
Image.open(
BytesIO(
base64.b64decode(
b"""
iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
LeNZUworuN1cjTPIzrTX6ofHWeo3v336qPzfEwRmBnHTtf95/fglZK5N0PDgfRTslpGBvz7LFc4F
IUXBWQGjQ5MGCx34EDFPwXiY4YbYxavpnhHFrk14CDAAAAD//wBlAJr/AgKqRooH2gAgPeggvUAA
Bu2WfgPoAwzRAABAAAAAAACQgLz/3Uv4Gv+gX7BJgDeeGP6AAAD1NMDzKHD7ANWr3loYbxsAD791
NAADfcoIDyP44K/jv4Y63/Z+t98Ovt+ub4T48LAAAAD//wBlAJr/AuplMlADJAAAAGuAphWpqhMx
in0A/fRvAYBABPgBwBUgABBQ/sYAyv9g0bCHgOLoGAAAAAAAREAAwI7nr0ArYpow7aX8//9LaP/9
SjdavWA8ePHeBIKB//81/83ndznOaXx379wAAAD//wBlAJr/AqDxW+D3AABAAbUh/QMnbQag/gAY
AYDAAACgtgD/gOqAAAB5IA/8AAAk+n9w0AAA8AAAmFRJuPo27ciC0cD5oeW4E7KA/wD3ECMAn2tt
y8PgwH8AfAxFzC0JzeAMtratAsC/ffwAAAD//wBlAJr/BGKAyCAA4AAAAvgeYTAwHd1kmQF5chkG
ABoMIHcL5xVpTfQbUqzlAAAErwAQBgAAEOClA5D9il08AEh/tUzdCBsXkbgACED+woQg8Si9VeqY
lODCn7lmF6NhnAEYgAAA/NMIAAAAAAD//2JgjLZgVGBg5Pv/Tvpc8hwGBjYGJADjHDrAwPzAjv/H
/Wf3PzCwtzcwHmBgYGcwbZz8wHaCAQMDOwMDQ8MCBgYOC3W7mp+f0w+wHOYxO3OG+e376hsMZjk3
AAAAAP//YmCMY2A4wMAIN5e5gQETPD6AZisDAwMDgzSDAAPjByiHcQMDAwMDg1nOze1lByRu5/47
c4859311AYNZzg0AAAAA//9iYGDBYihOIIMuwIjGL39/fwffA8b//xv/P2BPtzzHwCBjUQAAAAD/
/yLFBrIBAAAA//9i1HhcwdhizX7u8NZNzyLbvT97bfrMf/QHI8evOwcSqGUJAAAA//9iYBB81iSw
pEE170Qrg5MIYydHqwdDQRMrAwcVrQAAAAD//2J4x7j9AAMDn8Q/BgYLBoaiAwwMjPdvMDBYM1Tv
oJodAAAAAP//Yqo/83+dxePWlxl3npsel9lvLfPcqlE9725C+acfVLMEAAAA//9i+s9gwCoaaGMR
evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA
AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v//
Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR
w7IkEbzhVQAAAABJRU5ErkJggg==
"""
)
)
),
)
return f