Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
ImageFont.py1310 linesDownload Raw Back to PIL
1#
2# The Python Imaging Library.
3# $Id$
4#
5# PIL raster font management
6#
7# History:
8# 1996-08-07 fl   created (experimental)
9# 1997-08-25 fl   minor adjustments to handle fonts from pilfont 0.3
10# 1999-02-06 fl   rewrote most font management stuff in C
11# 1999-03-17 fl   take pth files into account in load_path (from Richard Jones)
12# 2001-02-17 fl   added freetype support
13# 2001-05-09 fl   added TransposedFont wrapper class
14# 2002-03-04 fl   make sure we have a "L" or "1" font
15# 2002-12-04 fl   skip non-directory entries in the system path
16# 2003-04-29 fl   add embedded default font
17# 2003-09-27 fl   added support for truetype charmap encodings
18#
19# Todo:
20# Adapt to PILFONT2 format (16-bit fonts, compressed, single file)
21#
22# Copyright (c) 1997-2003 by Secret Labs AB
23# Copyright (c) 1996-2003 by Fredrik Lundh
24#
25# See the README file for information on usage and redistribution.
26#
27
28from __future__ import annotations
29
30import base64
31import os
32import sys
33import warnings
34from enum import IntEnum
35from io import BytesIO
36from types import ModuleType
37from typing import IO, Any, BinaryIO, TypedDict, cast
38
39from . import Image
40from ._typing import StrOrBytesPath
41from ._util import DeferredError, is_path
42
43TYPE_CHECKING = False
44if TYPE_CHECKING:
45    from . import ImageFile
46    from ._imaging import ImagingFont
47    from ._imagingft import Font
48
49
50class Axis(TypedDict):
51    minimum: int | None
52    default: int | None
53    maximum: int | None
54    name: bytes | None
55
56
57class Layout(IntEnum):
58    BASIC = 0
59    RAQM = 1
60
61
62MAX_STRING_LENGTH = 1_000_000
63
64
65core: ModuleType | DeferredError
66try:
67    from . import _imagingft as core
68except ImportError as ex:
69    core = DeferredError.new(ex)
70
71
72def _string_length_check(text: str | bytes | bytearray) -> None:
73    if MAX_STRING_LENGTH is not None and len(text) > MAX_STRING_LENGTH:
74        msg = "too many characters in string"
75        raise ValueError(msg)
76
77
78# FIXME: add support for pilfont2 format (see FontFile.py)
79
80# --------------------------------------------------------------------
81# Font metrics format:
82#       "PILfont" LF
83#       fontdescriptor LF
84#       (optional) key=value... LF
85#       "DATA" LF
86#       binary data: 256*10*2 bytes (dx, dy, dstbox, srcbox)
87#
88# To place a character, cut out srcbox and paste at dstbox,
89# relative to the character position.  Then move the character
90# position according to dx, dy.
91# --------------------------------------------------------------------
92
93
94class ImageFont:
95    """PIL font wrapper"""
96
97    font: ImagingFont
98
99    def _load_pilfont(self, filename: str) -> None:
100        with open(filename, "rb") as fp:
101            image: ImageFile.ImageFile | None = None
102            root = os.path.splitext(filename)[0]
103
104            for ext in (".png", ".gif", ".pbm"):
105                if image:
106                    image.close()
107                try:
108                    fullname = root + ext
109                    image = Image.open(fullname)
110                except Exception:
111                    pass
112                else:
113                    if image.mode in ("1", "L"):
114                        break
115            else:
116                if image:
117                    image.close()
118
119                msg = f"cannot find glyph data file {root}.{{gif|pbm|png}}"
120                raise OSError(msg)
121
122            self.file = fullname
123
124            self._load_pilfont_data(fp, image)
125            image.close()
126
127    def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None:
128        # check image
129        if image.mode not in ("1", "L"):
130            image.close()
131
132            msg = "invalid font image mode"
133            raise TypeError(msg)
134
135        # read PILfont header
136        if file.read(8) != b"PILfont\n":
137            image.close()
138
139            msg = "Not a PILfont file"
140            raise SyntaxError(msg)
141        file.readline()
142        self.info = []  # FIXME: should be a dictionary
143        while True:
144            s = file.readline()
145            if not s or s == b"DATA\n":
146                break
147            self.info.append(s)
148
149        # read PILfont metrics
150        data = file.read(256 * 20)
151
152        self._load(image, data)
153
154    def _load(self, image: Image.Image, data: bytes) -> None:
155        image.load()
156
157        self.font = Image.core.font(image.im, data)
158
159    def getmask(
160        self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any
161    ) -> Image.core.ImagingCore:
162        """
163        Create a bitmap for the text.
164
165        If the font uses antialiasing, the bitmap should have mode ``L`` and use a
166        maximum value of 255. Otherwise, it should have mode ``1``.
167
168        :param text: Text to render.
169        :param mode: Used by some graphics drivers to indicate what mode the
170                     driver prefers; if empty, the renderer may return either
171                     mode. Note that the mode is always a string, to simplify
172                     C-level implementations.
173
174                     .. versionadded:: 1.1.5
175
176        :return: An internal PIL storage memory instance as defined by the
177                 :py:mod:`PIL.Image.core` interface module.
178        """
179        _string_length_check(text)
180        Image._decompression_bomb_check(self.font.getsize(text))
181        return self.font.getmask(text, mode)
182
183    def getbbox(
184        self, text: str | bytes | bytearray, *args: Any, **kwargs: Any
185    ) -> tuple[int, int, int, int]:
186        """
187        Returns bounding box (in pixels) of given text.
188
189        .. versionadded:: 9.2.0
190
191        :param text: Text to render.
192
193        :return: ``(left, top, right, bottom)`` bounding box
194        """
195        _string_length_check(text)
196        width, height = self.font.getsize(text)
197        return 0, 0, width, height
198
199    def getlength(
200        self, text: str | bytes | bytearray, *args: Any, **kwargs: Any
201    ) -> int:
202        """
203        Returns length (in pixels) of given text.
204        This is the amount by which following text should be offset.
205
206        .. versionadded:: 9.2.0
207        """
208        _string_length_check(text)
209        width, height = self.font.getsize(text)
210        return width
211
212
213##
214# Wrapper for FreeType fonts.  Application code should use the
215# <b>truetype</b> factory function to create font objects.
216
217
218class FreeTypeFont:
219    """FreeType font wrapper (requires _imagingft service)"""
220
221    font: Font
222    font_bytes: bytes
223
224    def __init__(
225        self,
226        font: StrOrBytesPath | BinaryIO,
227        size: float = 10,
228        index: int = 0,
229        encoding: str = "",
230        layout_engine: Layout | None = None,
231    ) -> None:
232        # FIXME: use service provider instead
233
234        if isinstance(core, DeferredError):
235            raise core.ex
236
237        if size <= 0:
238            msg = f"font size must be greater than 0, not {size}"
239            raise ValueError(msg)
240
241        self.path = font
242        self.size = size
243        self.index = index
244        self.encoding = encoding
245
246        if layout_engine not in (Layout.BASIC, Layout.RAQM):
247            layout_engine = Layout.BASIC
248            if core.HAVE_RAQM:
249                layout_engine = Layout.RAQM
250        elif layout_engine == Layout.RAQM and not core.HAVE_RAQM:
251            warnings.warn(
252                "Raqm layout was requested, but Raqm is not available. "
253                "Falling back to basic layout."
254            )
255            layout_engine = Layout.BASIC
256
257        self.layout_engine = layout_engine
258
259        def load_from_bytes(f: IO[bytes]) -> None:
260            self.font_bytes = f.read()
261            self.font = core.getfont(
262                "", size, index, encoding, self.font_bytes, layout_engine
263            )
264
265        if is_path(font):
266            font = os.fspath(font)
267            if sys.platform == "win32":
268                font_bytes_path = font if isinstance(font, bytes) else font.encode()
269                try:
270                    font_bytes_path.decode("ascii")
271                except UnicodeDecodeError:
272                    # FreeType cannot load fonts with non-ASCII characters on Windows
273                    # So load it into memory first
274                    with open(font, "rb") as f:
275                        load_from_bytes(f)
276                    return
277            self.font = core.getfont(
278                font, size, index, encoding, layout_engine=layout_engine
279            )
280        else:
281            load_from_bytes(cast(IO[bytes], font))
282
283    def __getstate__(self) -> list[Any]:
284        return [self.path, self.size, self.index, self.encoding, self.layout_engine]
285
286    def __setstate__(self, state: list[Any]) -> None:
287        path, size, index, encoding, layout_engine = state
288        FreeTypeFont.__init__(self, path, size, index, encoding, layout_engine)
289
290    def getname(self) -> tuple[str | None, str | None]:
291        """
292        :return: A tuple of the font family (e.g. Helvetica) and the font style
293            (e.g. Bold)
294        """
295        return self.font.family, self.font.style
296
297    def getmetrics(self) -> tuple[int, int]:
298        """
299        :return: A tuple of the font ascent (the distance from the baseline to
300            the highest outline point) and descent (the distance from the
301            baseline to the lowest outline point, a negative value)
302        """
303        return self.font.ascent, self.font.descent
304
305    def getlength(
306        self,
307        text: str | bytes,
308        mode: str = "",
309        direction: str | None = None,
310        features: list[str] | None = None,
311        language: str | None = None,
312    ) -> float:
313        """
314        Returns length (in pixels with 1/64 precision) of given text when rendered
315        in font with provided direction, features, and language.
316
317        This is the amount by which following text should be offset.
318        Text bounding box may extend past the length in some fonts,
319        e.g. when using italics or accents.
320
321        The result is returned as a float; it is a whole number if using basic layout.
322
323        Note that the sum of two lengths may not equal the length of a concatenated
324        string due to kerning. If you need to adjust for kerning, include the following
325        character and subtract its length.
326
327        For example, instead of ::
328
329          hello = font.getlength("Hello")
330          world = font.getlength("World")
331          hello_world = hello + world  # not adjusted for kerning
332          assert hello_world == font.getlength("HelloWorld")  # may fail
333
334        use ::
335
336          hello = font.getlength("HelloW") - font.getlength("W")  # adjusted for kerning
337          world = font.getlength("World")
338          hello_world = hello + world  # adjusted for kerning
339          assert hello_world == font.getlength("HelloWorld")  # True
340
341        or disable kerning with (requires libraqm) ::
342
343          hello = draw.textlength("Hello", font, features=["-kern"])
344          world = draw.textlength("World", font, features=["-kern"])
345          hello_world = hello + world  # kerning is disabled, no need to adjust
346          assert hello_world == draw.textlength("HelloWorld", font, features=["-kern"])
347
348        .. versionadded:: 8.0.0
349
350        :param text: Text to measure.
351        :param mode: Used by some graphics drivers to indicate what mode the
352                     driver prefers; if empty, the renderer may return either
353                     mode. Note that the mode is always a string, to simplify
354                     C-level implementations.
355
356        :param direction: Direction of the text. It can be 'rtl' (right to
357                          left), 'ltr' (left to right) or 'ttb' (top to bottom).
358                          Requires libraqm.
359
360        :param features: A list of OpenType font features to be used during text
361                         layout. This is usually used to turn on optional
362                         font features that are not enabled by default,
363                         for example 'dlig' or 'ss01', but can be also
364                         used to turn off default font features for
365                         example '-liga' to disable ligatures or '-kern'
366                         to disable kerning.  To get all supported
367                         features, see
368                         https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
369                         Requires libraqm.
370
371        :param language: Language of the text. Different languages may use
372                         different glyph shapes or ligatures. This parameter tells
373                         the font which language the text is in, and to apply the
374                         correct substitutions as appropriate, if available.
375                         It should be a `BCP 47 language code
376                         <https://www.w3.org/International/articles/language-tags/>`_
377                         Requires libraqm.
378
379        :return: Either width for horizontal text, or height for vertical text.
380        """
381        _string_length_check(text)
382        return self.font.getlength(text, mode, direction, features, language) / 64
383
384    def getbbox(
385        self,
386        text: str | bytes,
387        mode: str = "",
388        direction: str | None = None,
389        features: list[str] | None = None,
390        language: str | None = None,
391        stroke_width: float = 0,
392        anchor: str | None = None,
393    ) -> tuple[float, float, float, float]:
394        """
395        Returns bounding box (in pixels) of given text relative to given anchor
396        when rendered in font with provided direction, features, and language.
397
398        Use :py:meth:`getlength()` to get the offset of following text with
399        1/64 pixel precision. The bounding box includes extra margins for
400        some fonts, e.g. italics or accents.
401
402        .. versionadded:: 8.0.0
403
404        :param text: Text to render.
405        :param mode: Used by some graphics drivers to indicate what mode the
406                     driver prefers; if empty, the renderer may return either
407                     mode. Note that the mode is always a string, to simplify
408                     C-level implementations.
409
410        :param direction: Direction of the text. It can be 'rtl' (right to
411                          left), 'ltr' (left to right) or 'ttb' (top to bottom).
412                          Requires libraqm.
413
414        :param features: A list of OpenType font features to be used during text
415                         layout. This is usually used to turn on optional
416                         font features that are not enabled by default,
417                         for example 'dlig' or 'ss01', but can be also
418                         used to turn off default font features for
419                         example '-liga' to disable ligatures or '-kern'
420                         to disable kerning.  To get all supported
421                         features, see
422                         https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
423                         Requires libraqm.
424
425        :param language: Language of the text. Different languages may use
426                         different glyph shapes or ligatures. This parameter tells
427                         the font which language the text is in, and to apply the
428                         correct substitutions as appropriate, if available.
429                         It should be a `BCP 47 language code
430                         <https://www.w3.org/International/articles/language-tags/>`_
431                         Requires libraqm.
432
433        :param stroke_width: The width of the text stroke.
434
435        :param anchor:  The text anchor alignment. Determines the relative location of
436                        the anchor to the text. The default alignment is top left,
437                        specifically ``la`` for horizontal text and ``lt`` for
438                        vertical text. See :ref:`text-anchors` for details.
439
440        :return: ``(left, top, right, bottom)`` bounding box
441        """
442        _string_length_check(text)
443        size, offset = self.font.getsize(
444            text, mode, direction, features, language, anchor
445        )
446        left, top = offset[0] - stroke_width, offset[1] - stroke_width
447        width, height = size[0] + 2 * stroke_width, size[1] + 2 * stroke_width
448        return left, top, left + width, top + height
449
450    def getmask(
451        self,
452        text: str | bytes,
453        mode: str = "",
454        direction: str | None = None,
455        features: list[str] | None = None,
456        language: str | None = None,
457        stroke_width: float = 0,
458        anchor: str | None = None,
459        ink: int = 0,
460        start: tuple[float, float] | None = None,
461    ) -> Image.core.ImagingCore:
462        """
463        Create a bitmap for the text.
464
465        If the font uses antialiasing, the bitmap should have mode ``L`` and use a
466        maximum value of 255. If the font has embedded color data, the bitmap
467        should have mode ``RGBA``. Otherwise, it should have mode ``1``.
468
469        :param text: Text to render.
470        :param mode: Used by some graphics drivers to indicate what mode the
471                     driver prefers; if empty, the renderer may return either
472                     mode. Note that the mode is always a string, to simplify
473                     C-level implementations.
474
475                     .. versionadded:: 1.1.5
476
477        :param direction: Direction of the text. It can be 'rtl' (right to
478                          left), 'ltr' (left to right) or 'ttb' (top to bottom).
479                          Requires libraqm.
480
481                          .. versionadded:: 4.2.0
482
483        :param features: A list of OpenType font features to be used during text
484                         layout. This is usually used to turn on optional
485                         font features that are not enabled by default,
486                         for example 'dlig' or 'ss01', but can be also
487                         used to turn off default font features for
488                         example '-liga' to disable ligatures or '-kern'
489                         to disable kerning.  To get all supported
490                         features, see
491                         https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
492                         Requires libraqm.
493
494                         .. versionadded:: 4.2.0
495
496        :param language: Language of the text. Different languages may use
497                         different glyph shapes or ligatures. This parameter tells
498                         the font which language the text is in, and to apply the
499                         correct substitutions as appropriate, if available.
500                         It should be a `BCP 47 language code
501                         <https://www.w3.org/International/articles/language-tags/>`_
502                         Requires libraqm.
503
504                         .. versionadded:: 6.0.0
505
506        :param stroke_width: The width of the text stroke.
507
508                         .. versionadded:: 6.2.0
509
510        :param anchor:  The text anchor alignment. Determines the relative location of
511                        the anchor to the text. The default alignment is top left,
512                        specifically ``la`` for horizontal text and ``lt`` for
513                        vertical text. See :ref:`text-anchors` for details.
514
515                         .. versionadded:: 8.0.0
516
517        :param ink: Foreground ink for rendering in RGBA mode.
518
519                         .. versionadded:: 8.0.0
520
521        :param start: Tuple of horizontal and vertical offset, as text may render
522                      differently when starting at fractional coordinates.
523
524                         .. versionadded:: 9.4.0
525
526        :return: An internal PIL storage memory instance as defined by the
527                 :py:mod:`PIL.Image.core` interface module.
528        """
529        return self.getmask2(
530            text,
531            mode,
532            direction=direction,
533            features=features,
534            language=language,
535            stroke_width=stroke_width,
536            anchor=anchor,
537            ink=ink,
538            start=start,
539        )[0]
540
541    def getmask2(
542        self,
543        text: str | bytes,
544        mode: str = "",
545        direction: str | None = None,
546        features: list[str] | None = None,
547        language: str | None = None,
548        stroke_width: float = 0,
549        anchor: str | None = None,
550        ink: int = 0,
551        start: tuple[float, float] | None = None,
552        *args: Any,
553        **kwargs: Any,
554    ) -> tuple[Image.core.ImagingCore, tuple[int, int]]:
555        """
556        Create a bitmap for the text.
557
558        If the font uses antialiasing, the bitmap should have mode ``L`` and use a
559        maximum value of 255. If the font has embedded color data, the bitmap
560        should have mode ``RGBA``. Otherwise, it should have mode ``1``.
561
562        :param text: Text to render.
563        :param mode: Used by some graphics drivers to indicate what mode the
564                     driver prefers; if empty, the renderer may return either
565                     mode. Note that the mode is always a string, to simplify
566                     C-level implementations.
567
568                     .. versionadded:: 1.1.5
569
570        :param direction: Direction of the text. It can be 'rtl' (right to
571                          left), 'ltr' (left to right) or 'ttb' (top to bottom).
572                          Requires libraqm.
573
574                          .. versionadded:: 4.2.0
575
576        :param features: A list of OpenType font features to be used during text
577                         layout. This is usually used to turn on optional
578                         font features that are not enabled by default,
579                         for example 'dlig' or 'ss01', but can be also
580                         used to turn off default font features for
581                         example '-liga' to disable ligatures or '-kern'
582                         to disable kerning.  To get all supported
583                         features, see
584                         https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist
585                         Requires libraqm.
586
587                         .. versionadded:: 4.2.0
588
589        :param language: Language of the text. Different languages may use
590                         different glyph shapes or ligatures. This parameter tells
591                         the font which language the text is in, and to apply the
592                         correct substitutions as appropriate, if available.
593                         It should be a `BCP 47 language code
594                         <https://www.w3.org/International/articles/language-tags/>`_
595                         Requires libraqm.
596
597                         .. versionadded:: 6.0.0
598
599        :param stroke_width: The width of the text stroke.
600
601                         .. versionadded:: 6.2.0
602
603        :param anchor:  The text anchor alignment. Determines the relative location of
604                        the anchor to the text. The default alignment is top left,
605                        specifically ``la`` for horizontal text and ``lt`` for
606                        vertical text. See :ref:`text-anchors` for details.
607
608                         .. versionadded:: 8.0.0
609
610        :param ink: Foreground ink for rendering in RGBA mode.
611
612                         .. versionadded:: 8.0.0
613
614        :param start: Tuple of horizontal and vertical offset, as text may render
615                      differently when starting at fractional coordinates.
616
617                         .. versionadded:: 9.4.0
618
619        :return: A tuple of an internal PIL storage memory instance as defined by the
620                 :py:mod:`PIL.Image.core` interface module, and the text offset, the
621                 gap between the starting coordinate and the first marking
622        """
623        _string_length_check(text)
624        if start is None:
625            start = (0, 0)
626
627        def fill(width: int, height: int) -> Image.core.ImagingCore:
628            size = (width, height)
629            Image._decompression_bomb_check(size)
630            return Image.core.fill("RGBA" if mode == "RGBA" else "L", size)
631
632        return self.font.render(
633            text,
634            fill,
635            mode,
636            direction,
637            features,
638            language,
639            stroke_width,
640            kwargs.get("stroke_filled", False),
641            anchor,
642            ink,
643            start,
644        )
645
646    def font_variant(
647        self,
648        font: StrOrBytesPath | BinaryIO | None = None,
649        size: float | None = None,
650        index: int | None = None,
651        encoding: str | None = None,
652        layout_engine: Layout | None = None,
653    ) -> FreeTypeFont:
654        """
655        Create a copy of this FreeTypeFont object,
656        using any specified arguments to override the settings.
657
658        Parameters are identical to the parameters used to initialize this
659        object.
660
661        :return: A FreeTypeFont object.
662        """
663        if font is None:
664            try:
665                font = BytesIO(self.font_bytes)
666            except AttributeError:
667                font = self.path
668        return FreeTypeFont(
669            font=font,
670            size=self.size if size is None else size,
671            index=self.index if index is None else index,
672            encoding=self.encoding if encoding is None else encoding,
673            layout_engine=layout_engine or self.layout_engine,
674        )
675
676    def get_variation_names(self) -> list[bytes]:
677        """
678        :returns: A list of the named styles in a variation font.
679        :exception OSError: If the font is not a variation font.
680        """
681        names = []
682        for name in self.font.getvarnames():
683            name = name.replace(b"\x00", b"")
684            if name not in names:
685                names.append(name)
686        return names
687
688    def set_variation_by_name(self, name: str | bytes) -> None:
689        """
690        :param name: The name of the style.
691        :exception OSError: If the font is not a variation font.
692        """
693        names = self.get_variation_names()
694        if not isinstance(name, bytes):
695            name = name.encode()
696        index = names.index(name) + 1
697
698        if index == getattr(self, "_last_variation_index", None):
699            # When the same name is set twice in a row,
700            # there is an 'unknown freetype error'
701            # https://savannah.nongnu.org/bugs/?56186
702            return
703        self._last_variation_index = index
704
705        self.font.setvarname(index)
706
707    def get_variation_axes(self) -> list[Axis]:
708        """
709        :returns: A list of the axes in a variation font.
710        :exception OSError: If the font is not a variation font.
711        """
712        axes = self.font.getvaraxes()
713        for axis in axes:
714            if axis["name"]:
715                axis["name"] = axis["name"].replace(b"\x00", b"")
716        return axes
717
718    def set_variation_by_axes(self, axes: list[float]) -> None:
719        """
720        :param axes: A list of values for each axis.
721        :exception OSError: If the font is not a variation font.
722        """
723        self.font.setvaraxes(axes)
724
725
726class TransposedFont:
727    """Wrapper for writing rotated or mirrored text"""
728
729    def __init__(
730        self, font: ImageFont | FreeTypeFont, orientation: Image.Transpose | None = None
731    ):
732        """
733        Wrapper that creates a transposed font from any existing font
734        object.
735
736        :param font: A font object.
737        :param orientation: An optional orientation.  If given, this should
738            be one of Image.Transpose.FLIP_LEFT_RIGHT, Image.Transpose.FLIP_TOP_BOTTOM,
739            Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_180, or
740            Image.Transpose.ROTATE_270.
741        """
742        self.font = font
743        self.orientation = orientation  # any 'transpose' argument, or None
744
745    def getmask(
746        self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any
747    ) -> Image.core.ImagingCore:
748        im = self.font.getmask(text, mode, *args, **kwargs)
749        if self.orientation is not None:
750            return im.transpose(self.orientation)
751        return im
752
753    def getbbox(
754        self, text: str | bytes, *args: Any, **kwargs: Any
755    ) -> tuple[int, int, float, float]:
756        # TransposedFont doesn't support getmask2, move top-left point to (0, 0)
757        # this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont
758        left, top, right, bottom = self.font.getbbox(text, *args, **kwargs)
759        width = right - left
760        height = bottom - top
761        if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
762            return 0, 0, height, width
763        return 0, 0, width, height
764
765    def getlength(self, text: str | bytes, *args: Any, **kwargs: Any) -> float:
766        if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
767            msg = "text length is undefined for text rotated by 90 or 270 degrees"
768            raise ValueError(msg)
769        return self.font.getlength(text, *args, **kwargs)
770
771
772def load(filename: str) -> ImageFont:
773    """
774    Load a font file. This function loads a font object from the given
775    bitmap font file, and returns the corresponding font object. For loading TrueType
776    or OpenType fonts instead, see :py:func:`~PIL.ImageFont.truetype`.
777
778    :param filename: Name of font file.
779    :return: A font object.
780    :exception OSError: If the file could not be read.
781    """
782    f = ImageFont()
783    f._load_pilfont(filename)
784    return f
785
786
787def truetype(
788    font: StrOrBytesPath | BinaryIO,
789    size: float = 10,
790    index: int = 0,
791    encoding: str = "",
792    layout_engine: Layout | None = None,
793) -> FreeTypeFont:
794    """
795    Load a TrueType or OpenType font from a file or file-like object,
796    and create a font object. This function loads a font object from the given
797    file or file-like object, and creates a font object for a font of the given
798    size. For loading bitmap fonts instead, see :py:func:`~PIL.ImageFont.load`
799    and :py:func:`~PIL.ImageFont.load_path`.
800
801    Pillow uses FreeType to open font files. On Windows, be aware that FreeType
802    will keep the file open as long as the FreeTypeFont object exists. Windows
803    limits the number of files that can be open in C at once to 512, so if many
804    fonts are opened simultaneously and that limit is approached, an
805    ``OSError`` may be thrown, reporting that FreeType "cannot open resource".
806    A workaround would be to copy the file(s) into memory, and open that instead.
807
808    This function requires the _imagingft service.
809
810    :param font: A filename or file-like object containing a TrueType font.
811                 If the file is not found in this filename, the loader may also
812                 search in other directories, such as:
813
814                 * The :file:`fonts/` directory on Windows,
815                 * :file:`/Library/Fonts/`, :file:`/System/Library/Fonts/`
816                   and :file:`~/Library/Fonts/` on macOS.
817                 * :file:`~/.local/share/fonts`, :file:`/usr/local/share/fonts`,
818                   and :file:`/usr/share/fonts` on Linux; or those specified by
819                   the ``XDG_DATA_HOME`` and ``XDG_DATA_DIRS`` environment variables
820                   for user-installed and system-wide fonts, respectively.
821
822    :param size: The requested size, in pixels.
823    :param index: Which font face to load (default is first available face).
824    :param encoding: Which font encoding to use (default is Unicode). Possible
825                     encodings include (see the FreeType documentation for more
826                     information):
827
828                     * "unic" (Unicode)
829                     * "symb" (Microsoft Symbol)
830                     * "ADOB" (Adobe Standard)
831                     * "ADBE" (Adobe Expert)
832                     * "ADBC" (Adobe Custom)
833                     * "armn" (Apple Roman)
834                     * "sjis" (Shift JIS)
835                     * "gb  " (PRC)
836                     * "big5"
837                     * "wans" (Extended Wansung)
838                     * "joha" (Johab)
839                     * "lat1" (Latin-1)
840
841                     This specifies the character set to use. It does not alter the
842                     encoding of any text provided in subsequent operations.
843    :param layout_engine: Which layout engine to use, if available:
844                     :attr:`.ImageFont.Layout.BASIC` or :attr:`.ImageFont.Layout.RAQM`.
845                     If it is available, Raqm layout will be used by default.
846                     Otherwise, basic layout will be used.
847
848                     Raqm layout is recommended for all non-English text. If Raqm layout
849                     is not required, basic layout will have better performance.
850
851                     You can check support for Raqm layout using
852                     :py:func:`PIL.features.check_feature` with ``feature="raqm"``.
853
854                     .. versionadded:: 4.2.0
855    :return: A font object.
856    :exception OSError: If the file could not be read.
857    :exception ValueError: If the font size is not greater than zero.
858    """
859
860    def freetype(font: StrOrBytesPath | BinaryIO) -> FreeTypeFont:
861        return FreeTypeFont(font, size, index, encoding, layout_engine)
862
863    try:
864        return freetype(font)
865    except OSError:
866        if not is_path(font):
867            raise
868        ttf_filename = os.path.basename(font)
869
870        dirs = []
871        if sys.platform == "win32":
872            # check the windows font repository
873            # NOTE: must use uppercase WINDIR, to work around bugs in
874            # 1.5.2's os.environ.get()
875            windir = os.environ.get("WINDIR")
876            if windir:
877                dirs.append(os.path.join(windir, "fonts"))
878        elif sys.platform in ("linux", "linux2"):
879            data_home = os.environ.get("XDG_DATA_HOME")
880            if not data_home:
881                # The freedesktop spec defines the following default directory for
882                # when XDG_DATA_HOME is unset or empty. This user-level directory
883                # takes precedence over system-level directories.
884                data_home = os.path.expanduser("~/.local/share")
885            xdg_dirs = [data_home]
886
887            data_dirs = os.environ.get("XDG_DATA_DIRS")
888            if not data_dirs:
889                # Similarly, defaults are defined for the system-level directories
890                data_dirs = "/usr/local/share:/usr/share"
891            xdg_dirs += data_dirs.split(":")
892
893            dirs += [os.path.join(xdg_dir, "fonts") for xdg_dir in xdg_dirs]
894        elif sys.platform == "darwin":
895            dirs += [
896                "/Library/Fonts",
897                "/System/Library/Fonts",
898                os.path.expanduser("~/Library/Fonts"),
899            ]
900
901        ext = os.path.splitext(ttf_filename)[1]
902        first_font_with_a_different_extension = None
903        for directory in dirs:
904            for walkroot, walkdir, walkfilenames in os.walk(directory):
905                for walkfilename in walkfilenames:
906                    if ext and walkfilename == ttf_filename:
907                        return freetype(os.path.join(walkroot, walkfilename))
908                    elif not ext and os.path.splitext(walkfilename)[0] == ttf_filename:
909                        fontpath = os.path.join(walkroot, walkfilename)
910                        if os.path.splitext(fontpath)[1] == ".ttf":
911                            return freetype(fontpath)
912                        if not ext and first_font_with_a_different_extension is None:
913                            first_font_with_a_different_extension = fontpath
914        if first_font_with_a_different_extension:
915            return freetype(first_font_with_a_different_extension)
916        raise
917
918
919def load_path(filename: str | bytes) -> ImageFont:
920    """
921    Load font file. Same as :py:func:`~PIL.ImageFont.load`, but searches for a
922    bitmap font along the Python path.
923
924    :param filename: Name of font file.
925    :return: A font object.
926    :exception OSError: If the file could not be read.
927    """
928    if not isinstance(filename, str):
929        filename = filename.decode("utf-8")
930    for directory in sys.path:
931        try:
932            return load(os.path.join(directory, filename))
933        except OSError:  # noqa: PERF203
934            pass
935    msg = f'cannot find font file "{filename}" in sys.path'
936    if os.path.exists(filename):
937        msg += f', did you mean ImageFont.load("{filename}") instead?'
938
939    raise OSError(msg)
940
941
942def load_default_imagefont() -> ImageFont:
943    f = ImageFont()
944    f._load_pilfont_data(
945        # courB08
946        BytesIO(base64.b64decode(b"""
947UElMZm9udAo7Ozs7OzsxMDsKREFUQQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
948AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
949AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
950AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
951AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
952AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
953AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
954AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
955AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
956AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
957AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
958AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAA//8AAQAAAAAAAAABAAEA
959BgAAAAH/+gADAAAAAQAAAAMABgAGAAAAAf/6AAT//QADAAAABgADAAYAAAAA//kABQABAAYAAAAL
960AAgABgAAAAD/+AAFAAEACwAAABAACQAGAAAAAP/5AAUAAAAQAAAAFQAHAAYAAP////oABQAAABUA
961AAAbAAYABgAAAAH/+QAE//wAGwAAAB4AAwAGAAAAAf/5AAQAAQAeAAAAIQAIAAYAAAAB//kABAAB
962ACEAAAAkAAgABgAAAAD/+QAE//0AJAAAACgABAAGAAAAAP/6AAX//wAoAAAALQAFAAYAAAAB//8A
963BAACAC0AAAAwAAMABgAAAAD//AAF//0AMAAAADUAAQAGAAAAAf//AAMAAAA1AAAANwABAAYAAAAB
964//kABQABADcAAAA7AAgABgAAAAD/+QAFAAAAOwAAAEAABwAGAAAAAP/5AAYAAABAAAAARgAHAAYA
965AAAA//kABQAAAEYAAABLAAcABgAAAAD/+QAFAAAASwAAAFAABwAGAAAAAP/5AAYAAABQAAAAVgAH
966AAYAAAAA//kABQAAAFYAAABbAAcABgAAAAD/+QAFAAAAWwAAAGAABwAGAAAAAP/5AAUAAABgAAAA
967ZQAHAAYAAAAA//kABQAAAGUAAABqAAcABgAAAAD/+QAFAAAAagAAAG8ABwAGAAAAAf/8AAMAAABv
968AAAAcQAEAAYAAAAA//wAAwACAHEAAAB0AAYABgAAAAD/+gAE//8AdAAAAHgABQAGAAAAAP/7AAT/
969/gB4AAAAfAADAAYAAAAB//oABf//AHwAAACAAAUABgAAAAD/+gAFAAAAgAAAAIUABgAGAAAAAP/5
970AAYAAQCFAAAAiwAIAAYAAP////oABgAAAIsAAACSAAYABgAA////+gAFAAAAkgAAAJgABgAGAAAA
971AP/6AAUAAACYAAAAnQAGAAYAAP////oABQAAAJ0AAACjAAYABgAA////+gAFAAAAowAAAKkABgAG
972AAD////6AAUAAACpAAAArwAGAAYAAAAA//oABQAAAK8AAAC0AAYABgAA////+gAGAAAAtAAAALsA
973BgAGAAAAAP/6AAQAAAC7AAAAvwAGAAYAAP////oABQAAAL8AAADFAAYABgAA////+gAGAAAAxQAA
974AMwABgAGAAD////6AAUAAADMAAAA0gAGAAYAAP////oABQAAANIAAADYAAYABgAA////+gAGAAAA
9752AAAAN8ABgAGAAAAAP/6AAUAAADfAAAA5AAGAAYAAP////oABQAAAOQAAADqAAYABgAAAAD/+gAF
976AAEA6gAAAO8ABwAGAAD////6AAYAAADvAAAA9gAGAAYAAAAA//oABQAAAPYAAAD7AAYABgAA////
977+gAFAAAA+wAAAQEABgAGAAD////6AAYAAAEBAAABCAAGAAYAAP////oABgAAAQgAAAEPAAYABgAA
978////+gAGAAABDwAAARYABgAGAAAAAP/6AAYAAAEWAAABHAAGAAYAAP////oABgAAARwAAAEjAAYA
979BgAAAAD/+gAFAAABIwAAASgABgAGAAAAAf/5AAQAAQEoAAABKwAIAAYAAAAA//kABAABASsAAAEv
980AAgABgAAAAH/+QAEAAEBLwAAATIACAAGAAAAAP/5AAX//AEyAAABNwADAAYAAAAAAAEABgACATcA
981AAE9AAEABgAAAAH/+QAE//wBPQAAAUAAAwAGAAAAAP/7AAYAAAFAAAABRgAFAAYAAP////kABQAA
982AUYAAAFMAAcABgAAAAD/+wAFAAABTAAAAVEABQAGAAAAAP/5AAYAAAFRAAABVwAHAAYAAAAA//sA
983BQAAAVcAAAFcAAUABgAAAAD/+QAFAAABXAAAAWEABwAGAAAAAP/7AAYAAgFhAAABZwAHAAYAAP//
984//kABQAAAWcAAAFtAAcABgAAAAD/+QAGAAABbQAAAXMABwAGAAAAAP/5AAQAAgFzAAABdwAJAAYA
985AP////kABgAAAXcAAAF+AAcABgAAAAD/+QAGAAABfgAAAYQABwAGAAD////7AAUAAAGEAAABigAF
986AAYAAP////sABQAAAYoAAAGQAAUABgAAAAD/+wAFAAABkAAAAZUABQAGAAD////7AAUAAgGVAAAB
987mwAHAAYAAAAA//sABgACAZsAAAGhAAcABgAAAAD/+wAGAAABoQAAAacABQAGAAAAAP/7AAYAAAGn
988AAABrQAFAAYAAAAA//kABgAAAa0AAAGzAAcABgAA////+wAGAAABswAAAboABQAGAAD////7AAUA
989AAG6AAABwAAFAAYAAP////sABgAAAcAAAAHHAAUABgAAAAD/+wAGAAABxwAAAc0ABQAGAAD////7
990AAYAAgHNAAAB1AAHAAYAAAAA//sABQAAAdQAAAHZAAUABgAAAAH/+QAFAAEB2QAAAd0ACAAGAAAA
991Av/6AAMAAQHdAAAB3gAHAAYAAAAA//kABAABAd4AAAHiAAgABgAAAAD/+wAF//0B4gAAAecAAgAA
992AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
993AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
994AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
995AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
996AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
997AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
998AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
999AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1000AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1001AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1002AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1003AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYAAAAB
1004//sAAwACAecAAAHpAAcABgAAAAD/+QAFAAEB6QAAAe4ACAAGAAAAAP/5AAYAAAHuAAAB9AAHAAYA
1005AAAA//oABf//AfQAAAH5AAUABgAAAAD/+QAGAAAB+QAAAf8ABwAGAAAAAv/5AAMAAgH/AAACAAAJ
1006AAYAAAAA//kABQABAgAAAAIFAAgABgAAAAH/+gAE//sCBQAAAggAAQAGAAAAAP/5AAYAAAIIAAAC
1007DgAHAAYAAAAB//kABf/+Ag4AAAISAAUABgAA////+wAGAAACEgAAAhkABQAGAAAAAP/7AAX//gIZ
1008AAACHgADAAYAAAAA//wABf/9Ah4AAAIjAAEABgAAAAD/+QAHAAACIwAAAioABwAGAAAAAP/6AAT/
1009+wIqAAACLgABAAYAAAAA//kABP/8Ai4AAAIyAAMABgAAAAD/+gAFAAACMgAAAjcABgAGAAAAAf/5
1010AAT//QI3AAACOgAEAAYAAAAB//kABP/9AjoAAAI9AAQABgAAAAL/+QAE//sCPQAAAj8AAgAGAAD/
1011///7AAYAAgI/AAACRgAHAAYAAAAA//kABgABAkYAAAJMAAgABgAAAAH//AAD//0CTAAAAk4AAQAG
1012AAAAAf//AAQAAgJOAAACUQADAAYAAAAB//kABP/9AlEAAAJUAAQABgAAAAH/+QAF//4CVAAAAlgA
1013BQAGAAD////7AAYAAAJYAAACXwAFAAYAAP////kABgAAAl8AAAJmAAcABgAA////+QAGAAACZgAA
1014Am0ABwAGAAD////5AAYAAAJtAAACdAAHAAYAAAAA//sABQACAnQAAAJ5AAcABgAA////9wAGAAAC
1015eQAAAoAACQAGAAD////3AAYAAAKAAAAChwAJAAYAAP////cABgAAAocAAAKOAAkABgAA////9wAG
1016AAACjgAAApUACQAGAAD////4AAYAAAKVAAACnAAIAAYAAP////cABgAAApwAAAKjAAkABgAA////
1017+gAGAAACowAAAqoABgAGAAAAAP/6AAUAAgKqAAACrwAIAAYAAP////cABQAAAq8AAAK1AAkABgAA
1018////9wAFAAACtQAAArsACQAGAAD////3AAUAAAK7AAACwQAJAAYAAP////gABQAAAsEAAALHAAgA
1019BgAAAAD/9wAEAAACxwAAAssACQAGAAAAAP/3AAQAAALLAAACzwAJAAYAAAAA//cABAAAAs8AAALT
1020AAkABgAAAAD/+AAEAAAC0wAAAtcACAAGAAD////6AAUAAALXAAAC3QAGAAYAAP////cABgAAAt0A
1021AALkAAkABgAAAAD/9wAFAAAC5AAAAukACQAGAAAAAP/3AAUAAALpAAAC7gAJAAYAAAAA//cABQAA
1022Au4AAALzAAkABgAAAAD/9wAFAAAC8wAAAvgACQAGAAAAAP/4AAUAAAL4AAAC/QAIAAYAAAAA//oA
1023Bf//Av0AAAMCAAUABgAA////+gAGAAADAgAAAwkABgAGAAD////3AAYAAAMJAAADEAAJAAYAAP//
1024//cABgAAAxAAAAMXAAkABgAA////9wAGAAADFwAAAx4ACQAGAAD////4AAYAAAAAAAoABwASAAYA
1025AP////cABgAAAAcACgAOABMABgAA////+gAFAAAADgAKABQAEAAGAAD////6AAYAAAAUAAoAGwAQ
1026AAYAAAAA//gABgAAABsACgAhABIABgAAAAD/+AAGAAAAIQAKACcAEgAGAAAAAP/4AAYAAAAnAAoA
1027LQASAAYAAAAA//gABgAAAC0ACgAzABIABgAAAAD/+QAGAAAAMwAKADkAEQAGAAAAAP/3AAYAAAA5
1028AAoAPwATAAYAAP////sABQAAAD8ACgBFAA8ABgAAAAD/+wAFAAIARQAKAEoAEQAGAAAAAP/4AAUA
1029AABKAAoATwASAAYAAAAA//gABQAAAE8ACgBUABIABgAAAAD/+AAFAAAAVAAKAFkAEgAGAAAAAP/5
1030AAUAAABZAAoAXgARAAYAAAAA//gABgAAAF4ACgBkABIABgAAAAD/+AAGAAAAZAAKAGoAEgAGAAAA
1031AP/4AAYAAABqAAoAcAASAAYAAAAA//kABgAAAHAACgB2ABEABgAAAAD/+AAFAAAAdgAKAHsAEgAG
1032AAD////4AAYAAAB7AAoAggASAAYAAAAA//gABQAAAIIACgCHABIABgAAAAD/+AAFAAAAhwAKAIwA
1033EgAGAAAAAP/4AAUAAACMAAoAkQASAAYAAAAA//gABQAAAJEACgCWABIABgAAAAD/+QAFAAAAlgAK
1034AJsAEQAGAAAAAP/6AAX//wCbAAoAoAAPAAYAAAAA//oABQABAKAACgClABEABgAA////+AAGAAAA
1035pQAKAKwAEgAGAAD////4AAYAAACsAAoAswASAAYAAP////gABgAAALMACgC6ABIABgAA////+QAG
1036AAAAugAKAMEAEQAGAAD////4AAYAAgDBAAoAyAAUAAYAAP////kABQACAMgACgDOABMABgAA////
1037+QAGAAIAzgAKANUAEw==
1038""")),
1039        Image.open(BytesIO(base64.b64decode(b"""
1040iVBORw0KGgoAAAANSUhEUgAAAx4AAAAUAQAAAAArMtZoAAAEwElEQVR4nABlAJr/AHVE4czCI/4u
1041Mc4b7vuds/xzjz5/3/7u/n9vMe7vnfH/9++vPn/xyf5zhxzjt8GHw8+2d83u8x27199/nxuQ6Od9
1042M43/5z2I+9n9ZtmDBwMQECDRQw/eQIQohJXxpBCNVE6QCCAAAAD//wBlAJr/AgALyj1t/wINwq0g
1043LeNZUworuN1cjTPIzrTX6ofHWeo3v336qPzfEwRmBnHTtf95/fglZK5N0PDgfRTslpGBvz7LFc4F
1044IUXBWQGjQ5MGCx34EDFPwXiY4YbYxavpnhHFrk14CDAAAAD//wBlAJr/AgKqRooH2gAgPeggvUAA
1045Bu2WfgPoAwzRAABAAAAAAACQgLz/3Uv4Gv+gX7BJgDeeGP6AAAD1NMDzKHD7ANWr3loYbxsAD791
1046NAADfcoIDyP44K/jv4Y63/Z+t98Ovt+ub4T48LAAAAD//wBlAJr/AuplMlADJAAAAGuAphWpqhMx
1047in0A/fRvAYBABPgBwBUgABBQ/sYAyv9g0bCHgOLoGAAAAAAAREAAwI7nr0ArYpow7aX8//9LaP/9
1048SjdavWA8ePHeBIKB//81/83ndznOaXx379wAAAD//wBlAJr/AqDxW+D3AABAAbUh/QMnbQag/gAY
1049AYDAAACgtgD/gOqAAAB5IA/8AAAk+n9w0AAA8AAAmFRJuPo27ciC0cD5oeW4E7KA/wD3ECMAn2tt
1050y8PgwH8AfAxFzC0JzeAMtratAsC/ffwAAAD//wBlAJr/BGKAyCAA4AAAAvgeYTAwHd1kmQF5chkG
1051ABoMIHcL5xVpTfQbUqzlAAAErwAQBgAAEOClA5D9il08AEh/tUzdCBsXkbgACED+woQg8Si9VeqY
1052lODCn7lmF6NhnAEYgAAA/NMIAAAAAAD//2JgjLZgVGBg5Pv/Tvpc8hwGBjYGJADjHDrAwPzAjv/H
1053/Wf3PzCwtzcwHmBgYGcwbZz8wHaCAQMDOwMDQ8MCBgYOC3W7mp+f0w+wHOYxO3OG+e376hsMZjk3
1054AAAAAP//YmCMY2A4wMAIN5e5gQETPD6AZisDAwMDgzSDAAPjByiHcQMDAwMDg1nOze1lByRu5/47
1055c4859311AYNZzg0AAAAA//9iYGDBYihOIIMuwIjGL39/fwffA8b//xv/P2BPtzzHwCBjUQAAAAD/
1056/yLFBrIBAAAA//9i1HhcwdhizX7u8NZNzyLbvT97bfrMf/QHI8evOwcSqGUJAAAA//9iYBB81iSw
1057pEE170Qrg5MIYydHqwdDQRMrAwcVrQAAAAD//2J4x7j9AAMDn8Q/BgYLBoaiAwwMjPdvMDBYM1Tv
1058oJodAAAAAP//Yqo/83+dxePWlxl3npsel9lvLfPcqlE9725C+acfVLMEAAAA//9i+s9gwCoaaGMR
1059evta/58PTEWzr21hufPjA8N+qlnBwAAAAAD//2JiWLci5v1+HmFXDqcnULE/MxgYGBj+f6CaJQAA
1060AAD//2Ji2FrkY3iYpYC5qDeGgeEMAwPDvwQBBoYvcTwOVLMEAAAA//9isDBgkP///0EOg9z35v//
1061Gc/eeW7BwPj5+QGZhANUswMAAAD//2JgqGBgYGBgqEMXlvhMPUsAAAAA//8iYDd1AAAAAP//AwDR
1062w7IkEbzhVQAAAABJRU5ErkJggg==
1063"""))),
1064    )
1065    return f
1066
1067
1068def load_default(size: float | None = None) -> FreeTypeFont | ImageFont:
1069    """If FreeType support is available, load a version of Aileron Regular,
1070    https://dotcolon.net/fonts/aileron, with a more limited character set.
1071
1072    Otherwise, load a "better than nothing" font.
1073
1074    .. versionadded:: 1.1.4
1075
1076    :param size: The font size of Aileron Regular.
1077
1078        .. versionadded:: 10.1.0
1079
1080    :return: A font object.
1081    """
1082    if isinstance(core, ModuleType) or size is not None:
1083        return truetype(
1084            BytesIO(base64.b64decode(b"""
1085AAEAAAAPAIAAAwBwRkZUTYwDlUAAADFoAAAAHEdERUYAqADnAAAo8AAAACRHUE9ThhmITwAAKfgAA
1086AduR1NVQnHxefoAACkUAAAA4k9TLzJovoHLAAABeAAAAGBjbWFw5lFQMQAAA6gAAAGqZ2FzcP//AA
1087MAACjoAAAACGdseWYmRXoPAAAGQAAAHfhoZWFkE18ayQAAAPwAAAA2aGhlYQboArEAAAE0AAAAJGh
1088tdHjjERZ8AAAB2AAAAdBsb2NhuOexrgAABVQAAADqbWF4cAC7AEYAAAFYAAAAIG5hbWUr+h5lAAAk
1089OAAAA6Jwb3N0D3oPTQAAJ9wAAAEKAAEAAAABGhxJDqIhXw889QALA+gAAAAA0Bqf2QAAAADhCh2h/
10902r/LgOxAyAAAAAIAAIAAAAAAAAAAQAAA8r/GgAAA7j/av9qA7EAAQAAAAAAAAAAAAAAAAAAAHQAAQ
1091AAAHQAQwAFAAAAAAACAAAAAQABAAAAQAAAAAAAAAADAfoBkAAFAAgCigJYAAAASwKKAlgAAAFeADI
1092BPgAAAAAFAAAAAAAAAAAAAAcAAAAAAAAAAAAAAABVS1dOAEAAIPsCAwL/GgDIA8oA5iAAAJMAAAAA
1093AhICsgAAACAAAwH0AAAAAAAAAU0AAADYAAAA8gA5AVMAVgJEAEYCRAA1AuQAKQKOAEAAsAArATsAZ
1094AE7AB4CMABVAkQAUADc/+EBEgAgANwAJQEv//sCRAApAkQAggJEADwCRAAtAkQAIQJEADkCRAArAk
1095QAMgJEACwCRAAxANwAJQDc/+ECRABnAkQAUAJEAEQB8wAjA1QANgJ/AB0CcwBkArsALwLFAGQCSwB
1096kAjcAZALGAC8C2gBkAQgAZAIgADcCYQBkAj8AZANiAGQCzgBkAuEALwJWAGQC3QAvAmsAZAJJADQC
1097ZAAiAqoAXgJuACADuAAaAnEAGQJFABMCTwAuATMAYgEv//sBJwAiAkQAUAH0ADIBLAApAhMAJAJjA
1098EoCEQAeAmcAHgIlAB4BIgAVAmcAHgJRAEoA7gA+AOn/8wIKAEoA9wBGA1cASgJRAEoCSgAeAmMASg
1099JnAB4BSgBKAcsAGAE5ABQCUABCAgIAAQMRAAEB4v/6AgEAAQHOABQBLwBAAPoAYAEvACECRABNA0Y
1100AJAItAHgBKgAcAkQAUAEsAHQAygAgAi0AOQD3ADYA9wAWAaEANgGhABYCbAAlAYMAeAGDADkA6/9q
1101AhsAFAIKABUB/QAVAAAAAwAAAAMAAAAcAAEAAAAAAKQAAwABAAAAHAAEAIgAAAAeABAAAwAOAH4Aq
1102QCrALEAtAC3ALsgGSAdICYgOiBEISL7Av//AAAAIACpAKsAsAC0ALcAuyAYIBwgJiA5IEQhIvsB//
1103//4/+5/7j/tP+y/7D/reBR4E/gR+A14CzfTwVxAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
1104AAAAAAAEGAAABAAAAAAAAAAECAAAAAgAAAAAAAAAAAAAAAAAAAAEAAAMEBQYHCAkKCwwNDg8QERIT
1105FBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMT
1106U5PUFFSU1RVVldYWVpbXF1eX2BhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGQAAA
1107AAAAAAYnFmAAAAAABlAAAAAAAAAAAAAAAAAAAAAAAAAAAAY2htAAAAAAAAAABrbGlqAAAAAHAAbm9
1108ycwBnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAmACYAJgAmAD4AUgCCAMoBCgFO
1109AVwBcgGIAaYBvAHKAdYB6AH2AgwCIAJKAogCpgLWAw4DIgNkA5wDugPUA+gD/AQQBEYEogS8BPoFJ
1110gVSBWoFgAWwBcoF1gX6BhQGJAZMBmgGiga0BuIHGgdUB2YHkAeiB8AH3AfyCAoIHAgqCDoITghcCG
1111oIogjSCPoJKglYCXwJwgnqCgIKKApACl4Klgq8CtwLDAs8C1YLjAuyC9oL7gwMDCYMSAxgDKAMrAz
1112qDQoNTA1mDYQNoA2uDcAN2g3oDfYODA4iDkoOXA5sDnoOnA7EDvwAAAAFAAAAAAH0ArwAAwAGAAkA
1113DAAPAAAxESERAxMhExcRASELARETAfT6qv6syKr+jgFUqsiqArz9RAGLAP/+1P8B/v3VAP8BLP4CA
1114P8AAgA5//IAuQKyAAMACwAANyMDMwIyFhQGIiY0oE4MZk84JCQ4JLQB/v3AJDgkJDgAAgBWAeUBPA
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
1160vArIACwAAJRUhESEVIRUhFSEVAi/+NQHB/pUBTf6zRkYCskbwRvAAAAABAGQAAAIlArIACQAAExUh
1161FSERIxEhFboBQ/69VgHBAmzwRv7KArJGAAAAAAEAL//yAo8CwAAfAAABMxEjNQcGIyImNTQ2MzIWH
1162wEjJyYjIgYVFBYzMjY1IwGP90wfPnWTprSSf48LAVYCIqJofHllVG+hAU3+s3hARsicn8uAagoKpq
1163N/gaN1XAAAAAEAZAAAAowCsgALAAABESMRIREjETMRIRECjFb+hFZWAXwCsv1OAS7+0gKy/sQBPAA
1164AAAABAGQAAAC6ArIAAwAAMyMRM7pWVgKyAAABADf/8gHoArIAEwAAAREUBw4BIyImLwEzFxYzMjc2
1165NREB6AIFcGpgbQIBVgIHfXQKAQKy/lYxIltob2EpKYyEFD0BpwAAAAABAGQAAAJ0ArIACwAACQEjA
1166wcVIxEzEQEzATsBJ3ntQlZWAVVlAWH+nwEnR+ACsv6RAW8AAQBkAAACLwKyAAUAACUVIREzEQIv/j
1167VWRkYCsv2UAAABAGQAAAMUArIAFAAAAREjETQ3BgcDIwMmJxYVESMRMxsBAxRWAiMxemx8NxsCVo7
1168MywKy/U4BY7ZLco7+nAFmoFxLtP6dArL9lwJpAAAAAAEAZAAAAoACsgANAAAhIwEWFREjETMBJjUR
1169MwKAhP67A1aEAUUDVAJeeov+pwKy/aJ5jAFZAAAAAgAv//ICuwLAAAkAEwAAEiAWFRQGICY1NBIyN
1170jU0JiIGFRTbATSsrP7MrNrYenrYegLAxaKhxsahov47nIeIm5uIhwACAGQAAAJHArIADgAYAAABHg
1171EVFAYHBisBESMRMzITNjQnJisBETMyAZRUX2VOHzuAVtY7GlxcGDWIiDUCrgtnVlVpCgT+5gKy/rU
1172V1BUF/vgAAAACAC//zAK9AsAAEgAcAAAlFhcHJiMiBwYjIiY1NDYgFhUUJRQWMjY1NCYiBgI9PUMx
1173UDcfKh8omqysATSs/dR62Hp62HpICTg7NgkHxqGixcWitbWHnJyHiJubAAIAZAAAAlgCsgAXACMAA
1174CUWFyMmJyYnJisBESMRMzIXHgEVFAYHFiUzMjc+ATU0JyYrAQIqDCJfGQwNWhAhglbiOx9QXEY1Tv
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

Showing the first 1,200 of 1310 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai