@ai-ecoverse/py-pillow 12.3.0-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +30 -0
- package/README.md +4 -0
- package/lib/python3.14/site-packages/PIL/AvifImagePlugin.py +295 -0
- package/lib/python3.14/site-packages/PIL/BdfFontFile.py +124 -0
- package/lib/python3.14/site-packages/PIL/BlpImagePlugin.py +498 -0
- package/lib/python3.14/site-packages/PIL/BmpImagePlugin.py +514 -0
- package/lib/python3.14/site-packages/PIL/BufrStubImagePlugin.py +72 -0
- package/lib/python3.14/site-packages/PIL/ContainerIO.py +173 -0
- package/lib/python3.14/site-packages/PIL/CurImagePlugin.py +103 -0
- package/lib/python3.14/site-packages/PIL/DcxImagePlugin.py +84 -0
- package/lib/python3.14/site-packages/PIL/DdsImagePlugin.py +632 -0
- package/lib/python3.14/site-packages/PIL/EpsImagePlugin.py +484 -0
- package/lib/python3.14/site-packages/PIL/ExifTags.py +384 -0
- package/lib/python3.14/site-packages/PIL/FitsImagePlugin.py +153 -0
- package/lib/python3.14/site-packages/PIL/FliImagePlugin.py +184 -0
- package/lib/python3.14/site-packages/PIL/FontFile.py +160 -0
- package/lib/python3.14/site-packages/PIL/FpxImagePlugin.py +262 -0
- package/lib/python3.14/site-packages/PIL/FtexImagePlugin.py +115 -0
- package/lib/python3.14/site-packages/PIL/GbrImagePlugin.py +103 -0
- package/lib/python3.14/site-packages/PIL/GdImageFile.py +104 -0
- package/lib/python3.14/site-packages/PIL/GifImagePlugin.py +1223 -0
- package/lib/python3.14/site-packages/PIL/GimpGradientFile.py +154 -0
- package/lib/python3.14/site-packages/PIL/GimpPaletteFile.py +75 -0
- package/lib/python3.14/site-packages/PIL/GribStubImagePlugin.py +72 -0
- package/lib/python3.14/site-packages/PIL/Hdf5StubImagePlugin.py +72 -0
- package/lib/python3.14/site-packages/PIL/IcnsImagePlugin.py +401 -0
- package/lib/python3.14/site-packages/PIL/IcoImagePlugin.py +396 -0
- package/lib/python3.14/site-packages/PIL/ImImagePlugin.py +390 -0
- package/lib/python3.14/site-packages/PIL/Image.py +4385 -0
- package/lib/python3.14/site-packages/PIL/ImageChops.py +311 -0
- package/lib/python3.14/site-packages/PIL/ImageCms.py +1078 -0
- package/lib/python3.14/site-packages/PIL/ImageColor.py +320 -0
- package/lib/python3.14/site-packages/PIL/ImageDraw.py +1002 -0
- package/lib/python3.14/site-packages/PIL/ImageDraw2.py +244 -0
- package/lib/python3.14/site-packages/PIL/ImageEnhance.py +113 -0
- package/lib/python3.14/site-packages/PIL/ImageFile.py +938 -0
- package/lib/python3.14/site-packages/PIL/ImageFilter.py +617 -0
- package/lib/python3.14/site-packages/PIL/ImageFont.py +1330 -0
- package/lib/python3.14/site-packages/PIL/ImageGrab.py +234 -0
- package/lib/python3.14/site-packages/PIL/ImageMath.py +314 -0
- package/lib/python3.14/site-packages/PIL/ImageMode.py +85 -0
- package/lib/python3.14/site-packages/PIL/ImageMorph.py +317 -0
- package/lib/python3.14/site-packages/PIL/ImageOps.py +749 -0
- package/lib/python3.14/site-packages/PIL/ImagePalette.py +290 -0
- package/lib/python3.14/site-packages/PIL/ImagePath.py +20 -0
- package/lib/python3.14/site-packages/PIL/ImageQt.py +219 -0
- package/lib/python3.14/site-packages/PIL/ImageSequence.py +88 -0
- package/lib/python3.14/site-packages/PIL/ImageShow.py +367 -0
- package/lib/python3.14/site-packages/PIL/ImageStat.py +167 -0
- package/lib/python3.14/site-packages/PIL/ImageText.py +509 -0
- package/lib/python3.14/site-packages/PIL/ImageTk.py +266 -0
- package/lib/python3.14/site-packages/PIL/ImageTransform.py +136 -0
- package/lib/python3.14/site-packages/PIL/ImageWin.py +247 -0
- package/lib/python3.14/site-packages/PIL/ImtImagePlugin.py +103 -0
- package/lib/python3.14/site-packages/PIL/IptcImagePlugin.py +226 -0
- package/lib/python3.14/site-packages/PIL/Jpeg2KImagePlugin.py +466 -0
- package/lib/python3.14/site-packages/PIL/JpegImagePlugin.py +889 -0
- package/lib/python3.14/site-packages/PIL/JpegPresets.py +242 -0
- package/lib/python3.14/site-packages/PIL/McIdasImagePlugin.py +78 -0
- package/lib/python3.14/site-packages/PIL/MicImagePlugin.py +103 -0
- package/lib/python3.14/site-packages/PIL/MpegImagePlugin.py +84 -0
- package/lib/python3.14/site-packages/PIL/MpoImagePlugin.py +203 -0
- package/lib/python3.14/site-packages/PIL/MspImagePlugin.py +200 -0
- package/lib/python3.14/site-packages/PIL/PSDraw.py +238 -0
- package/lib/python3.14/site-packages/PIL/PaletteFile.py +54 -0
- package/lib/python3.14/site-packages/PIL/PalmImagePlugin.py +217 -0
- package/lib/python3.14/site-packages/PIL/PcdImagePlugin.py +68 -0
- package/lib/python3.14/site-packages/PIL/PcfFontFile.py +256 -0
- package/lib/python3.14/site-packages/PIL/PcxImagePlugin.py +232 -0
- package/lib/python3.14/site-packages/PIL/PdfImagePlugin.py +315 -0
- package/lib/python3.14/site-packages/PIL/PdfParser.py +1092 -0
- package/lib/python3.14/site-packages/PIL/PixarImagePlugin.py +72 -0
- package/lib/python3.14/site-packages/PIL/PngImagePlugin.py +1578 -0
- package/lib/python3.14/site-packages/PIL/PpmImagePlugin.py +375 -0
- package/lib/python3.14/site-packages/PIL/PsdImagePlugin.py +339 -0
- package/lib/python3.14/site-packages/PIL/QoiImagePlugin.py +235 -0
- package/lib/python3.14/site-packages/PIL/SgiImagePlugin.py +231 -0
- package/lib/python3.14/site-packages/PIL/SpiderImagePlugin.py +332 -0
- package/lib/python3.14/site-packages/PIL/SunImagePlugin.py +145 -0
- package/lib/python3.14/site-packages/PIL/TarIO.py +61 -0
- package/lib/python3.14/site-packages/PIL/TgaImagePlugin.py +284 -0
- package/lib/python3.14/site-packages/PIL/TiffImagePlugin.py +2354 -0
- package/lib/python3.14/site-packages/PIL/TiffTags.py +566 -0
- package/lib/python3.14/site-packages/PIL/WalImageFile.py +129 -0
- package/lib/python3.14/site-packages/PIL/WebPImagePlugin.py +322 -0
- package/lib/python3.14/site-packages/PIL/WmfImagePlugin.py +183 -0
- package/lib/python3.14/site-packages/PIL/XVThumbImagePlugin.py +83 -0
- package/lib/python3.14/site-packages/PIL/XbmImagePlugin.py +98 -0
- package/lib/python3.14/site-packages/PIL/XpmImagePlugin.py +157 -0
- package/lib/python3.14/site-packages/PIL/__init__.py +87 -0
- package/lib/python3.14/site-packages/PIL/__main__.py +7 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/AvifImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/BdfFontFile.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/BlpImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/BmpImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/BufrStubImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ContainerIO.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/CurImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/DcxImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/DdsImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/EpsImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ExifTags.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/FitsImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/FliImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/FontFile.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/FpxImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/FtexImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/GbrImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/GdImageFile.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/GifImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/GimpGradientFile.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/GimpPaletteFile.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/GribStubImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/Hdf5StubImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/IcnsImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/IcoImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/Image.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageChops.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageCms.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageColor.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageDraw.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageDraw2.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageEnhance.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageFile.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageFilter.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageFont.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageGrab.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageMath.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageMode.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageMorph.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageOps.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImagePalette.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImagePath.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageQt.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageSequence.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageShow.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageStat.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageText.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageTk.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageTransform.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImageWin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/ImtImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/IptcImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/Jpeg2KImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/JpegImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/JpegPresets.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/McIdasImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/MicImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/MpegImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/MpoImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/MspImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/PSDraw.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/PaletteFile.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/PalmImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/PcdImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/PcfFontFile.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/PcxImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/PdfImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/PdfParser.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/PixarImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/PngImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/PpmImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/PsdImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/QoiImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/SgiImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/SpiderImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/SunImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/TarIO.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/TgaImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/TiffImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/TiffTags.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/WalImageFile.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/WebPImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/WmfImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/XVThumbImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/XbmImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/XpmImagePlugin.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/__init__.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/__main__.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/_binary.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/_deprecate.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/_tkinter_finder.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/_typing.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/_util.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/_version.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/features.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/__pycache__/report.cpython-314.pyc +0 -0
- package/lib/python3.14/site-packages/PIL/_avif.pyi +3 -0
- package/lib/python3.14/site-packages/PIL/_binary.py +113 -0
- package/lib/python3.14/site-packages/PIL/_deprecate.py +72 -0
- package/lib/python3.14/site-packages/PIL/_imaging.cpython-314-wasm32-wasix.so +0 -0
- package/lib/python3.14/site-packages/PIL/_imaging.pyi +31 -0
- package/lib/python3.14/site-packages/PIL/_imagingcms.pyi +143 -0
- package/lib/python3.14/site-packages/PIL/_imagingft.cpython-314-wasm32-wasix.so +0 -0
- package/lib/python3.14/site-packages/PIL/_imagingft.pyi +70 -0
- package/lib/python3.14/site-packages/PIL/_imagingmath.cpython-314-wasm32-wasix.so +0 -0
- package/lib/python3.14/site-packages/PIL/_imagingmath.pyi +3 -0
- package/lib/python3.14/site-packages/PIL/_imagingmorph.cpython-314-wasm32-wasix.so +0 -0
- package/lib/python3.14/site-packages/PIL/_imagingmorph.pyi +3 -0
- package/lib/python3.14/site-packages/PIL/_imagingtk.cpython-314-wasm32-wasix.so +0 -0
- package/lib/python3.14/site-packages/PIL/_imagingtk.pyi +3 -0
- package/lib/python3.14/site-packages/PIL/_tkinter_finder.py +20 -0
- package/lib/python3.14/site-packages/PIL/_typing.py +45 -0
- package/lib/python3.14/site-packages/PIL/_util.py +29 -0
- package/lib/python3.14/site-packages/PIL/_version.py +4 -0
- package/lib/python3.14/site-packages/PIL/_webp.pyi +3 -0
- package/lib/python3.14/site-packages/PIL/features.py +343 -0
- package/lib/python3.14/site-packages/PIL/py.typed +0 -0
- package/lib/python3.14/site-packages/PIL/report.py +5 -0
- package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/METADATA +180 -0
- package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/RECORD +117 -0
- package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/WHEEL +5 -0
- package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/licenses/LICENSE +30 -0
- package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/top_level.txt +1 -0
- package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/zip-safe +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import re
|
|
5
|
+
from typing import AnyStr, Generic, NamedTuple
|
|
6
|
+
|
|
7
|
+
from . import ImageFont
|
|
8
|
+
from ._typing import _Ink
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _Line(NamedTuple):
|
|
12
|
+
x: float
|
|
13
|
+
y: float
|
|
14
|
+
anchor: str
|
|
15
|
+
text: str | bytes
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _Wrap(Generic[AnyStr]):
|
|
19
|
+
lines: list[AnyStr] = []
|
|
20
|
+
position = 0
|
|
21
|
+
offset = 0
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
text: Text[AnyStr],
|
|
26
|
+
width: int,
|
|
27
|
+
height: int | None = None,
|
|
28
|
+
font: ImageFont.BaseImageFont | None = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
self.text: Text[AnyStr] = text
|
|
31
|
+
self.width = width
|
|
32
|
+
self.height = height
|
|
33
|
+
self.font = font
|
|
34
|
+
|
|
35
|
+
input_text = self.text.text
|
|
36
|
+
emptystring = "" if isinstance(input_text, str) else b""
|
|
37
|
+
line = emptystring
|
|
38
|
+
|
|
39
|
+
for word in re.findall(
|
|
40
|
+
r"\s*\S+" if isinstance(input_text, str) else rb"\s*\S+", input_text
|
|
41
|
+
):
|
|
42
|
+
newlines = re.findall(
|
|
43
|
+
r"[^\S\n]*\n" if isinstance(input_text, str) else rb"[^\S\n]*\n", word
|
|
44
|
+
)
|
|
45
|
+
if newlines:
|
|
46
|
+
if not self.add_line(line):
|
|
47
|
+
break
|
|
48
|
+
for i, line in enumerate(newlines):
|
|
49
|
+
if i != 0 and not self.add_line(emptystring):
|
|
50
|
+
break
|
|
51
|
+
self.position += len(line)
|
|
52
|
+
word = word[len(line) :]
|
|
53
|
+
line = emptystring
|
|
54
|
+
|
|
55
|
+
new_line = line + word
|
|
56
|
+
if self.text._get_bbox(new_line, self.font)[2] <= width:
|
|
57
|
+
# This word fits on the line
|
|
58
|
+
line = new_line
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
# This word does not fit on the line
|
|
62
|
+
if line and not self.add_line(line):
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
original_length = len(word)
|
|
66
|
+
word = word.lstrip()
|
|
67
|
+
self.offset = original_length - len(word)
|
|
68
|
+
|
|
69
|
+
if self.text._get_bbox(word, self.font)[2] > width:
|
|
70
|
+
if font is None:
|
|
71
|
+
msg = "Word does not fit within line"
|
|
72
|
+
raise ValueError(msg)
|
|
73
|
+
break
|
|
74
|
+
line = word
|
|
75
|
+
else:
|
|
76
|
+
if line:
|
|
77
|
+
self.add_line(line)
|
|
78
|
+
self.remaining_text: AnyStr = input_text[self.position :]
|
|
79
|
+
|
|
80
|
+
def add_line(self, line: AnyStr) -> bool:
|
|
81
|
+
lines = self.lines + [line]
|
|
82
|
+
if self.height is not None:
|
|
83
|
+
last_line_y = self.text._split(lines=lines)[-1].y
|
|
84
|
+
last_line_height = self.text._get_bbox(line, self.font)[3]
|
|
85
|
+
if last_line_y + last_line_height > self.height:
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
self.lines = lines
|
|
89
|
+
self.position += len(line) + self.offset
|
|
90
|
+
self.offset = 0
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class Text(Generic[AnyStr]):
|
|
95
|
+
def __init__(
|
|
96
|
+
self,
|
|
97
|
+
text: AnyStr,
|
|
98
|
+
font: ImageFont.BaseImageFont | None = None,
|
|
99
|
+
mode: str = "RGB",
|
|
100
|
+
spacing: float = 4,
|
|
101
|
+
direction: str | None = None,
|
|
102
|
+
features: list[str] | None = None,
|
|
103
|
+
language: str | None = None,
|
|
104
|
+
) -> None:
|
|
105
|
+
"""
|
|
106
|
+
:param text: String to be drawn.
|
|
107
|
+
:param font: Either an :py:class:`~PIL.ImageFont.ImageFont` instance,
|
|
108
|
+
:py:class:`~PIL.ImageFont.FreeTypeFont` instance,
|
|
109
|
+
:py:class:`~PIL.ImageFont.TransposedFont` instance or ``None``. If
|
|
110
|
+
``None``, the default font from :py:meth:`.ImageFont.load_default`
|
|
111
|
+
will be used.
|
|
112
|
+
:param mode: The image mode this will be used with.
|
|
113
|
+
:param spacing: The number of pixels between lines.
|
|
114
|
+
:param direction: Direction of the text. It can be ``"rtl"`` (right to left),
|
|
115
|
+
``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
|
116
|
+
Requires libraqm.
|
|
117
|
+
:param features: A list of OpenType font features to be used during text
|
|
118
|
+
layout. This is usually used to turn on optional font features
|
|
119
|
+
that are not enabled by default, for example ``"dlig"`` or
|
|
120
|
+
``"ss01"``, but can be also used to turn off default font
|
|
121
|
+
features, for example ``"-liga"`` to disable ligatures or
|
|
122
|
+
``"-kern"`` to disable kerning. To get all supported
|
|
123
|
+
features, see `OpenType docs`_.
|
|
124
|
+
Requires libraqm.
|
|
125
|
+
:param language: Language of the text. Different languages may use
|
|
126
|
+
different glyph shapes or ligatures. This parameter tells
|
|
127
|
+
the font which language the text is in, and to apply the
|
|
128
|
+
correct substitutions as appropriate, if available.
|
|
129
|
+
It should be a `BCP 47 language code`_.
|
|
130
|
+
Requires libraqm.
|
|
131
|
+
"""
|
|
132
|
+
self.text: AnyStr = text
|
|
133
|
+
self.font = font or ImageFont.load_default()
|
|
134
|
+
|
|
135
|
+
self.mode = mode
|
|
136
|
+
self.spacing = spacing
|
|
137
|
+
self.direction = direction
|
|
138
|
+
self.features = features
|
|
139
|
+
self.language = language
|
|
140
|
+
|
|
141
|
+
self.embedded_color = False
|
|
142
|
+
|
|
143
|
+
self.stroke_width: float = 0
|
|
144
|
+
self.stroke_fill: _Ink | None = None
|
|
145
|
+
|
|
146
|
+
def embed_color(self) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Use embedded color glyphs (COLR, CBDT, SBIX).
|
|
149
|
+
"""
|
|
150
|
+
if self.mode not in ("RGB", "RGBA"):
|
|
151
|
+
msg = "Embedded color supported only in RGB and RGBA modes"
|
|
152
|
+
raise ValueError(msg)
|
|
153
|
+
self.embedded_color = True
|
|
154
|
+
|
|
155
|
+
def stroke(self, width: float = 0, fill: _Ink | None = None) -> None:
|
|
156
|
+
"""
|
|
157
|
+
:param width: The width of the text stroke.
|
|
158
|
+
:param fill: Color to use for the text stroke when drawing. If not given, will
|
|
159
|
+
default to the ``fill`` parameter from
|
|
160
|
+
:py:meth:`.ImageDraw.ImageDraw.text`.
|
|
161
|
+
"""
|
|
162
|
+
self.stroke_width = width
|
|
163
|
+
self.stroke_fill = fill
|
|
164
|
+
|
|
165
|
+
def _get_fontmode(self) -> str:
|
|
166
|
+
if self.mode in ("1", "P", "I", "F"):
|
|
167
|
+
return "1"
|
|
168
|
+
elif self.embedded_color:
|
|
169
|
+
return "RGBA"
|
|
170
|
+
else:
|
|
171
|
+
return "L"
|
|
172
|
+
|
|
173
|
+
def wrap(
|
|
174
|
+
self,
|
|
175
|
+
width: int,
|
|
176
|
+
height: int | None = None,
|
|
177
|
+
scaling: str | tuple[str, int] | None = None,
|
|
178
|
+
) -> Text[AnyStr] | None:
|
|
179
|
+
"""
|
|
180
|
+
Wrap text to fit within a given width.
|
|
181
|
+
|
|
182
|
+
:param width: The width to fit within.
|
|
183
|
+
:param height: An optional height limit. Any text that does not fit within this
|
|
184
|
+
will be returned as a new :py:class:`.Text` object.
|
|
185
|
+
:param scaling: An optional directive to scale the text, either "grow" as much
|
|
186
|
+
as possible within the given dimensions, or "shrink" until it
|
|
187
|
+
fits. It can also be a tuple of (direction, limit), with an
|
|
188
|
+
integer limit to stop scaling at.
|
|
189
|
+
|
|
190
|
+
:returns: An :py:class:`.Text` object, or None.
|
|
191
|
+
"""
|
|
192
|
+
if isinstance(self.font, ImageFont.TransposedFont):
|
|
193
|
+
msg = "TransposedFont not supported"
|
|
194
|
+
raise ValueError(msg)
|
|
195
|
+
if self.direction not in (None, "ltr"):
|
|
196
|
+
msg = "Only ltr direction supported"
|
|
197
|
+
raise ValueError(msg)
|
|
198
|
+
|
|
199
|
+
if scaling is None:
|
|
200
|
+
wrap = _Wrap(self, width, height)
|
|
201
|
+
else:
|
|
202
|
+
if not isinstance(self.font, ImageFont.FreeTypeFont):
|
|
203
|
+
msg = "'scaling' only supports FreeTypeFont"
|
|
204
|
+
raise ValueError(msg)
|
|
205
|
+
if height is None:
|
|
206
|
+
msg = "'scaling' requires 'height'"
|
|
207
|
+
raise ValueError(msg)
|
|
208
|
+
|
|
209
|
+
if isinstance(scaling, str):
|
|
210
|
+
limit = 1
|
|
211
|
+
else:
|
|
212
|
+
scaling, limit = scaling
|
|
213
|
+
|
|
214
|
+
font = self.font
|
|
215
|
+
wrap = _Wrap(self, width, height, font)
|
|
216
|
+
if scaling == "shrink":
|
|
217
|
+
if not wrap.remaining_text:
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
size = math.ceil(font.size)
|
|
221
|
+
while wrap.remaining_text:
|
|
222
|
+
if size == max(limit, 1):
|
|
223
|
+
msg = "Text could not be scaled"
|
|
224
|
+
raise ValueError(msg)
|
|
225
|
+
size -= 1
|
|
226
|
+
font = self.font.font_variant(size=size)
|
|
227
|
+
wrap = _Wrap(self, width, height, font)
|
|
228
|
+
self.font = font
|
|
229
|
+
else:
|
|
230
|
+
if wrap.remaining_text:
|
|
231
|
+
msg = "Text could not be scaled"
|
|
232
|
+
raise ValueError(msg)
|
|
233
|
+
|
|
234
|
+
size = math.floor(font.size)
|
|
235
|
+
while not wrap.remaining_text:
|
|
236
|
+
if size == limit:
|
|
237
|
+
msg = "Text could not be scaled"
|
|
238
|
+
raise ValueError(msg)
|
|
239
|
+
size += 1
|
|
240
|
+
font = self.font.font_variant(size=size)
|
|
241
|
+
last_wrap = wrap
|
|
242
|
+
wrap = _Wrap(self, width, height, font)
|
|
243
|
+
size -= 1
|
|
244
|
+
if size != self.font.size:
|
|
245
|
+
self.font = self.font.font_variant(size=size)
|
|
246
|
+
wrap = last_wrap
|
|
247
|
+
|
|
248
|
+
if wrap.remaining_text:
|
|
249
|
+
text = Text(
|
|
250
|
+
text=wrap.remaining_text,
|
|
251
|
+
font=self.font,
|
|
252
|
+
mode=self.mode,
|
|
253
|
+
spacing=self.spacing,
|
|
254
|
+
direction=self.direction,
|
|
255
|
+
features=self.features,
|
|
256
|
+
language=self.language,
|
|
257
|
+
)
|
|
258
|
+
text.embedded_color = self.embedded_color
|
|
259
|
+
text.stroke_width = self.stroke_width
|
|
260
|
+
text.stroke_fill = self.stroke_fill
|
|
261
|
+
else:
|
|
262
|
+
text = None
|
|
263
|
+
|
|
264
|
+
newline = "\n" if isinstance(self.text, str) else b"\n"
|
|
265
|
+
self.text = newline.join(wrap.lines)
|
|
266
|
+
return text
|
|
267
|
+
|
|
268
|
+
def get_length(self) -> float:
|
|
269
|
+
"""
|
|
270
|
+
Returns length (in pixels with 1/64 precision) of text.
|
|
271
|
+
|
|
272
|
+
This is the amount by which following text should be offset.
|
|
273
|
+
Text bounding box may extend past the length in some fonts,
|
|
274
|
+
e.g. when using italics or accents.
|
|
275
|
+
|
|
276
|
+
The result is returned as a float; it is a whole number if using basic layout.
|
|
277
|
+
|
|
278
|
+
Note that the sum of two lengths may not equal the length of a concatenated
|
|
279
|
+
string due to kerning. If you need to adjust for kerning, include the following
|
|
280
|
+
character and subtract its length.
|
|
281
|
+
|
|
282
|
+
For example, instead of::
|
|
283
|
+
|
|
284
|
+
hello = ImageText.Text("Hello", font).get_length()
|
|
285
|
+
world = ImageText.Text("World", font).get_length()
|
|
286
|
+
helloworld = ImageText.Text("HelloWorld", font).get_length()
|
|
287
|
+
assert hello + world == helloworld
|
|
288
|
+
|
|
289
|
+
use::
|
|
290
|
+
|
|
291
|
+
hello = (
|
|
292
|
+
ImageText.Text("HelloW", font).get_length() -
|
|
293
|
+
ImageText.Text("W", font).get_length()
|
|
294
|
+
) # adjusted for kerning
|
|
295
|
+
world = ImageText.Text("World", font).get_length()
|
|
296
|
+
helloworld = ImageText.Text("HelloWorld", font).get_length()
|
|
297
|
+
assert hello + world == helloworld
|
|
298
|
+
|
|
299
|
+
or disable kerning with (requires libraqm)::
|
|
300
|
+
|
|
301
|
+
hello = ImageText.Text("Hello", font, features=["-kern"]).get_length()
|
|
302
|
+
world = ImageText.Text("World", font, features=["-kern"]).get_length()
|
|
303
|
+
helloworld = ImageText.Text(
|
|
304
|
+
"HelloWorld", font, features=["-kern"]
|
|
305
|
+
).get_length()
|
|
306
|
+
assert hello + world == helloworld
|
|
307
|
+
|
|
308
|
+
:return: Either width for horizontal text, or height for vertical text.
|
|
309
|
+
"""
|
|
310
|
+
if isinstance(self.text, str):
|
|
311
|
+
multiline = "\n" in self.text
|
|
312
|
+
else:
|
|
313
|
+
multiline = b"\n" in self.text
|
|
314
|
+
if multiline:
|
|
315
|
+
msg = "can't measure length of multiline text"
|
|
316
|
+
raise ValueError(msg)
|
|
317
|
+
return self.font.getlength(
|
|
318
|
+
self.text,
|
|
319
|
+
self._get_fontmode(),
|
|
320
|
+
self.direction,
|
|
321
|
+
self.features,
|
|
322
|
+
self.language,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
def _split(
|
|
326
|
+
self,
|
|
327
|
+
xy: tuple[float, float] = (0, 0),
|
|
328
|
+
anchor: str | None = None,
|
|
329
|
+
align: str = "left",
|
|
330
|
+
lines: list[str] | list[bytes] | None = None,
|
|
331
|
+
) -> list[_Line]:
|
|
332
|
+
if anchor is None:
|
|
333
|
+
anchor = "lt" if self.direction == "ttb" else "la"
|
|
334
|
+
elif len(anchor) != 2:
|
|
335
|
+
msg = "anchor must be a 2 character string"
|
|
336
|
+
raise ValueError(msg)
|
|
337
|
+
|
|
338
|
+
if lines is None:
|
|
339
|
+
lines = (
|
|
340
|
+
self.text.split("\n")
|
|
341
|
+
if isinstance(self.text, str)
|
|
342
|
+
else self.text.split(b"\n")
|
|
343
|
+
)
|
|
344
|
+
if len(lines) == 1:
|
|
345
|
+
return [_Line(xy[0], xy[1], anchor, lines[0])]
|
|
346
|
+
|
|
347
|
+
if anchor[1] in "tb" and self.direction != "ttb":
|
|
348
|
+
msg = "anchor not supported for multiline text"
|
|
349
|
+
raise ValueError(msg)
|
|
350
|
+
|
|
351
|
+
fontmode = self._get_fontmode()
|
|
352
|
+
line_spacing = (
|
|
353
|
+
self.font.getbbox(
|
|
354
|
+
"A",
|
|
355
|
+
fontmode,
|
|
356
|
+
None,
|
|
357
|
+
self.features,
|
|
358
|
+
self.language,
|
|
359
|
+
self.stroke_width,
|
|
360
|
+
)[3]
|
|
361
|
+
+ self.stroke_width
|
|
362
|
+
+ self.spacing
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
top = xy[1]
|
|
366
|
+
parts = []
|
|
367
|
+
if self.direction == "ttb":
|
|
368
|
+
left = xy[0]
|
|
369
|
+
for line in lines:
|
|
370
|
+
parts.append(_Line(left, top, anchor, line))
|
|
371
|
+
left += line_spacing
|
|
372
|
+
else:
|
|
373
|
+
widths = []
|
|
374
|
+
max_width: float = 0
|
|
375
|
+
for line in lines:
|
|
376
|
+
line_width = self.font.getlength(
|
|
377
|
+
line, fontmode, self.direction, self.features, self.language
|
|
378
|
+
)
|
|
379
|
+
widths.append(line_width)
|
|
380
|
+
max_width = max(max_width, line_width)
|
|
381
|
+
|
|
382
|
+
if anchor[1] == "m":
|
|
383
|
+
top -= (len(lines) - 1) * line_spacing / 2.0
|
|
384
|
+
elif anchor[1] == "d":
|
|
385
|
+
top -= (len(lines) - 1) * line_spacing
|
|
386
|
+
|
|
387
|
+
idx = -1
|
|
388
|
+
for line in lines:
|
|
389
|
+
left = xy[0]
|
|
390
|
+
idx += 1
|
|
391
|
+
width_difference = max_width - widths[idx]
|
|
392
|
+
|
|
393
|
+
# align by align parameter
|
|
394
|
+
if align in ("left", "justify"):
|
|
395
|
+
pass
|
|
396
|
+
elif align == "center":
|
|
397
|
+
left += width_difference / 2.0
|
|
398
|
+
elif align == "right":
|
|
399
|
+
left += width_difference
|
|
400
|
+
else:
|
|
401
|
+
msg = 'align must be "left", "center", "right" or "justify"'
|
|
402
|
+
raise ValueError(msg)
|
|
403
|
+
|
|
404
|
+
if (
|
|
405
|
+
align == "justify"
|
|
406
|
+
and width_difference != 0
|
|
407
|
+
and idx != len(lines) - 1
|
|
408
|
+
):
|
|
409
|
+
words = (
|
|
410
|
+
line.split(" ") if isinstance(line, str) else line.split(b" ")
|
|
411
|
+
)
|
|
412
|
+
if len(words) > 1:
|
|
413
|
+
# align left by anchor
|
|
414
|
+
if anchor[0] == "m":
|
|
415
|
+
left -= max_width / 2.0
|
|
416
|
+
elif anchor[0] == "r":
|
|
417
|
+
left -= max_width
|
|
418
|
+
|
|
419
|
+
word_widths = [
|
|
420
|
+
self.font.getlength(
|
|
421
|
+
word,
|
|
422
|
+
fontmode,
|
|
423
|
+
self.direction,
|
|
424
|
+
self.features,
|
|
425
|
+
self.language,
|
|
426
|
+
)
|
|
427
|
+
for word in words
|
|
428
|
+
]
|
|
429
|
+
word_anchor = "l" + anchor[1]
|
|
430
|
+
width_difference = max_width - sum(word_widths)
|
|
431
|
+
i = 0
|
|
432
|
+
for word in words:
|
|
433
|
+
parts.append(_Line(left, top, word_anchor, word))
|
|
434
|
+
left += word_widths[i] + width_difference / (len(words) - 1)
|
|
435
|
+
i += 1
|
|
436
|
+
top += line_spacing
|
|
437
|
+
continue
|
|
438
|
+
|
|
439
|
+
# align left by anchor
|
|
440
|
+
if anchor[0] == "m":
|
|
441
|
+
left -= width_difference / 2.0
|
|
442
|
+
elif anchor[0] == "r":
|
|
443
|
+
left -= width_difference
|
|
444
|
+
parts.append(_Line(left, top, anchor, line))
|
|
445
|
+
top += line_spacing
|
|
446
|
+
|
|
447
|
+
return parts
|
|
448
|
+
|
|
449
|
+
def _get_bbox(
|
|
450
|
+
self,
|
|
451
|
+
text: str | bytes,
|
|
452
|
+
font: ImageFont.BaseImageFont | None = None,
|
|
453
|
+
anchor: str | None = None,
|
|
454
|
+
) -> tuple[float, float, float, float]:
|
|
455
|
+
return (font or self.font).getbbox(
|
|
456
|
+
text,
|
|
457
|
+
self._get_fontmode(),
|
|
458
|
+
self.direction,
|
|
459
|
+
self.features,
|
|
460
|
+
self.language,
|
|
461
|
+
self.stroke_width,
|
|
462
|
+
anchor,
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
def get_bbox(
|
|
466
|
+
self,
|
|
467
|
+
xy: tuple[float, float] = (0, 0),
|
|
468
|
+
anchor: str | None = None,
|
|
469
|
+
align: str = "left",
|
|
470
|
+
) -> tuple[float, float, float, float]:
|
|
471
|
+
"""
|
|
472
|
+
Returns bounding box (in pixels) of text.
|
|
473
|
+
|
|
474
|
+
Use :py:meth:`get_length` to get the offset of following text with 1/64 pixel
|
|
475
|
+
precision. The bounding box includes extra margins for some fonts, e.g. italics
|
|
476
|
+
or accents.
|
|
477
|
+
|
|
478
|
+
:param xy: The anchor coordinates of the text.
|
|
479
|
+
:param anchor: The text anchor alignment. Determines the relative location of
|
|
480
|
+
the anchor to the text. The default alignment is top left,
|
|
481
|
+
specifically ``la`` for horizontal text and ``lt`` for
|
|
482
|
+
vertical text. See :ref:`text-anchors` for details.
|
|
483
|
+
:param align: For multiline text, ``"left"``, ``"center"``, ``"right"`` or
|
|
484
|
+
``"justify"`` determines the relative alignment of lines. Use the
|
|
485
|
+
``anchor`` parameter to specify the alignment to ``xy``.
|
|
486
|
+
|
|
487
|
+
:return: ``(left, top, right, bottom)`` bounding box
|
|
488
|
+
"""
|
|
489
|
+
bbox: tuple[float, float, float, float] | None = None
|
|
490
|
+
for x, y, anchor, text in self._split(xy, anchor, align):
|
|
491
|
+
bbox_line = self._get_bbox(text, anchor=anchor)
|
|
492
|
+
bbox_line = (
|
|
493
|
+
bbox_line[0] + x,
|
|
494
|
+
bbox_line[1] + y,
|
|
495
|
+
bbox_line[2] + x,
|
|
496
|
+
bbox_line[3] + y,
|
|
497
|
+
)
|
|
498
|
+
if bbox is None:
|
|
499
|
+
bbox = bbox_line
|
|
500
|
+
else:
|
|
501
|
+
bbox = (
|
|
502
|
+
min(bbox[0], bbox_line[0]),
|
|
503
|
+
min(bbox[1], bbox_line[1]),
|
|
504
|
+
max(bbox[2], bbox_line[2]),
|
|
505
|
+
max(bbox[3], bbox_line[3]),
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
assert bbox is not None
|
|
509
|
+
return bbox
|