@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.
Files changed (217) hide show
  1. package/LICENSE +30 -0
  2. package/README.md +4 -0
  3. package/lib/python3.14/site-packages/PIL/AvifImagePlugin.py +295 -0
  4. package/lib/python3.14/site-packages/PIL/BdfFontFile.py +124 -0
  5. package/lib/python3.14/site-packages/PIL/BlpImagePlugin.py +498 -0
  6. package/lib/python3.14/site-packages/PIL/BmpImagePlugin.py +514 -0
  7. package/lib/python3.14/site-packages/PIL/BufrStubImagePlugin.py +72 -0
  8. package/lib/python3.14/site-packages/PIL/ContainerIO.py +173 -0
  9. package/lib/python3.14/site-packages/PIL/CurImagePlugin.py +103 -0
  10. package/lib/python3.14/site-packages/PIL/DcxImagePlugin.py +84 -0
  11. package/lib/python3.14/site-packages/PIL/DdsImagePlugin.py +632 -0
  12. package/lib/python3.14/site-packages/PIL/EpsImagePlugin.py +484 -0
  13. package/lib/python3.14/site-packages/PIL/ExifTags.py +384 -0
  14. package/lib/python3.14/site-packages/PIL/FitsImagePlugin.py +153 -0
  15. package/lib/python3.14/site-packages/PIL/FliImagePlugin.py +184 -0
  16. package/lib/python3.14/site-packages/PIL/FontFile.py +160 -0
  17. package/lib/python3.14/site-packages/PIL/FpxImagePlugin.py +262 -0
  18. package/lib/python3.14/site-packages/PIL/FtexImagePlugin.py +115 -0
  19. package/lib/python3.14/site-packages/PIL/GbrImagePlugin.py +103 -0
  20. package/lib/python3.14/site-packages/PIL/GdImageFile.py +104 -0
  21. package/lib/python3.14/site-packages/PIL/GifImagePlugin.py +1223 -0
  22. package/lib/python3.14/site-packages/PIL/GimpGradientFile.py +154 -0
  23. package/lib/python3.14/site-packages/PIL/GimpPaletteFile.py +75 -0
  24. package/lib/python3.14/site-packages/PIL/GribStubImagePlugin.py +72 -0
  25. package/lib/python3.14/site-packages/PIL/Hdf5StubImagePlugin.py +72 -0
  26. package/lib/python3.14/site-packages/PIL/IcnsImagePlugin.py +401 -0
  27. package/lib/python3.14/site-packages/PIL/IcoImagePlugin.py +396 -0
  28. package/lib/python3.14/site-packages/PIL/ImImagePlugin.py +390 -0
  29. package/lib/python3.14/site-packages/PIL/Image.py +4385 -0
  30. package/lib/python3.14/site-packages/PIL/ImageChops.py +311 -0
  31. package/lib/python3.14/site-packages/PIL/ImageCms.py +1078 -0
  32. package/lib/python3.14/site-packages/PIL/ImageColor.py +320 -0
  33. package/lib/python3.14/site-packages/PIL/ImageDraw.py +1002 -0
  34. package/lib/python3.14/site-packages/PIL/ImageDraw2.py +244 -0
  35. package/lib/python3.14/site-packages/PIL/ImageEnhance.py +113 -0
  36. package/lib/python3.14/site-packages/PIL/ImageFile.py +938 -0
  37. package/lib/python3.14/site-packages/PIL/ImageFilter.py +617 -0
  38. package/lib/python3.14/site-packages/PIL/ImageFont.py +1330 -0
  39. package/lib/python3.14/site-packages/PIL/ImageGrab.py +234 -0
  40. package/lib/python3.14/site-packages/PIL/ImageMath.py +314 -0
  41. package/lib/python3.14/site-packages/PIL/ImageMode.py +85 -0
  42. package/lib/python3.14/site-packages/PIL/ImageMorph.py +317 -0
  43. package/lib/python3.14/site-packages/PIL/ImageOps.py +749 -0
  44. package/lib/python3.14/site-packages/PIL/ImagePalette.py +290 -0
  45. package/lib/python3.14/site-packages/PIL/ImagePath.py +20 -0
  46. package/lib/python3.14/site-packages/PIL/ImageQt.py +219 -0
  47. package/lib/python3.14/site-packages/PIL/ImageSequence.py +88 -0
  48. package/lib/python3.14/site-packages/PIL/ImageShow.py +367 -0
  49. package/lib/python3.14/site-packages/PIL/ImageStat.py +167 -0
  50. package/lib/python3.14/site-packages/PIL/ImageText.py +509 -0
  51. package/lib/python3.14/site-packages/PIL/ImageTk.py +266 -0
  52. package/lib/python3.14/site-packages/PIL/ImageTransform.py +136 -0
  53. package/lib/python3.14/site-packages/PIL/ImageWin.py +247 -0
  54. package/lib/python3.14/site-packages/PIL/ImtImagePlugin.py +103 -0
  55. package/lib/python3.14/site-packages/PIL/IptcImagePlugin.py +226 -0
  56. package/lib/python3.14/site-packages/PIL/Jpeg2KImagePlugin.py +466 -0
  57. package/lib/python3.14/site-packages/PIL/JpegImagePlugin.py +889 -0
  58. package/lib/python3.14/site-packages/PIL/JpegPresets.py +242 -0
  59. package/lib/python3.14/site-packages/PIL/McIdasImagePlugin.py +78 -0
  60. package/lib/python3.14/site-packages/PIL/MicImagePlugin.py +103 -0
  61. package/lib/python3.14/site-packages/PIL/MpegImagePlugin.py +84 -0
  62. package/lib/python3.14/site-packages/PIL/MpoImagePlugin.py +203 -0
  63. package/lib/python3.14/site-packages/PIL/MspImagePlugin.py +200 -0
  64. package/lib/python3.14/site-packages/PIL/PSDraw.py +238 -0
  65. package/lib/python3.14/site-packages/PIL/PaletteFile.py +54 -0
  66. package/lib/python3.14/site-packages/PIL/PalmImagePlugin.py +217 -0
  67. package/lib/python3.14/site-packages/PIL/PcdImagePlugin.py +68 -0
  68. package/lib/python3.14/site-packages/PIL/PcfFontFile.py +256 -0
  69. package/lib/python3.14/site-packages/PIL/PcxImagePlugin.py +232 -0
  70. package/lib/python3.14/site-packages/PIL/PdfImagePlugin.py +315 -0
  71. package/lib/python3.14/site-packages/PIL/PdfParser.py +1092 -0
  72. package/lib/python3.14/site-packages/PIL/PixarImagePlugin.py +72 -0
  73. package/lib/python3.14/site-packages/PIL/PngImagePlugin.py +1578 -0
  74. package/lib/python3.14/site-packages/PIL/PpmImagePlugin.py +375 -0
  75. package/lib/python3.14/site-packages/PIL/PsdImagePlugin.py +339 -0
  76. package/lib/python3.14/site-packages/PIL/QoiImagePlugin.py +235 -0
  77. package/lib/python3.14/site-packages/PIL/SgiImagePlugin.py +231 -0
  78. package/lib/python3.14/site-packages/PIL/SpiderImagePlugin.py +332 -0
  79. package/lib/python3.14/site-packages/PIL/SunImagePlugin.py +145 -0
  80. package/lib/python3.14/site-packages/PIL/TarIO.py +61 -0
  81. package/lib/python3.14/site-packages/PIL/TgaImagePlugin.py +284 -0
  82. package/lib/python3.14/site-packages/PIL/TiffImagePlugin.py +2354 -0
  83. package/lib/python3.14/site-packages/PIL/TiffTags.py +566 -0
  84. package/lib/python3.14/site-packages/PIL/WalImageFile.py +129 -0
  85. package/lib/python3.14/site-packages/PIL/WebPImagePlugin.py +322 -0
  86. package/lib/python3.14/site-packages/PIL/WmfImagePlugin.py +183 -0
  87. package/lib/python3.14/site-packages/PIL/XVThumbImagePlugin.py +83 -0
  88. package/lib/python3.14/site-packages/PIL/XbmImagePlugin.py +98 -0
  89. package/lib/python3.14/site-packages/PIL/XpmImagePlugin.py +157 -0
  90. package/lib/python3.14/site-packages/PIL/__init__.py +87 -0
  91. package/lib/python3.14/site-packages/PIL/__main__.py +7 -0
  92. package/lib/python3.14/site-packages/PIL/__pycache__/AvifImagePlugin.cpython-314.pyc +0 -0
  93. package/lib/python3.14/site-packages/PIL/__pycache__/BdfFontFile.cpython-314.pyc +0 -0
  94. package/lib/python3.14/site-packages/PIL/__pycache__/BlpImagePlugin.cpython-314.pyc +0 -0
  95. package/lib/python3.14/site-packages/PIL/__pycache__/BmpImagePlugin.cpython-314.pyc +0 -0
  96. package/lib/python3.14/site-packages/PIL/__pycache__/BufrStubImagePlugin.cpython-314.pyc +0 -0
  97. package/lib/python3.14/site-packages/PIL/__pycache__/ContainerIO.cpython-314.pyc +0 -0
  98. package/lib/python3.14/site-packages/PIL/__pycache__/CurImagePlugin.cpython-314.pyc +0 -0
  99. package/lib/python3.14/site-packages/PIL/__pycache__/DcxImagePlugin.cpython-314.pyc +0 -0
  100. package/lib/python3.14/site-packages/PIL/__pycache__/DdsImagePlugin.cpython-314.pyc +0 -0
  101. package/lib/python3.14/site-packages/PIL/__pycache__/EpsImagePlugin.cpython-314.pyc +0 -0
  102. package/lib/python3.14/site-packages/PIL/__pycache__/ExifTags.cpython-314.pyc +0 -0
  103. package/lib/python3.14/site-packages/PIL/__pycache__/FitsImagePlugin.cpython-314.pyc +0 -0
  104. package/lib/python3.14/site-packages/PIL/__pycache__/FliImagePlugin.cpython-314.pyc +0 -0
  105. package/lib/python3.14/site-packages/PIL/__pycache__/FontFile.cpython-314.pyc +0 -0
  106. package/lib/python3.14/site-packages/PIL/__pycache__/FpxImagePlugin.cpython-314.pyc +0 -0
  107. package/lib/python3.14/site-packages/PIL/__pycache__/FtexImagePlugin.cpython-314.pyc +0 -0
  108. package/lib/python3.14/site-packages/PIL/__pycache__/GbrImagePlugin.cpython-314.pyc +0 -0
  109. package/lib/python3.14/site-packages/PIL/__pycache__/GdImageFile.cpython-314.pyc +0 -0
  110. package/lib/python3.14/site-packages/PIL/__pycache__/GifImagePlugin.cpython-314.pyc +0 -0
  111. package/lib/python3.14/site-packages/PIL/__pycache__/GimpGradientFile.cpython-314.pyc +0 -0
  112. package/lib/python3.14/site-packages/PIL/__pycache__/GimpPaletteFile.cpython-314.pyc +0 -0
  113. package/lib/python3.14/site-packages/PIL/__pycache__/GribStubImagePlugin.cpython-314.pyc +0 -0
  114. package/lib/python3.14/site-packages/PIL/__pycache__/Hdf5StubImagePlugin.cpython-314.pyc +0 -0
  115. package/lib/python3.14/site-packages/PIL/__pycache__/IcnsImagePlugin.cpython-314.pyc +0 -0
  116. package/lib/python3.14/site-packages/PIL/__pycache__/IcoImagePlugin.cpython-314.pyc +0 -0
  117. package/lib/python3.14/site-packages/PIL/__pycache__/ImImagePlugin.cpython-314.pyc +0 -0
  118. package/lib/python3.14/site-packages/PIL/__pycache__/Image.cpython-314.pyc +0 -0
  119. package/lib/python3.14/site-packages/PIL/__pycache__/ImageChops.cpython-314.pyc +0 -0
  120. package/lib/python3.14/site-packages/PIL/__pycache__/ImageCms.cpython-314.pyc +0 -0
  121. package/lib/python3.14/site-packages/PIL/__pycache__/ImageColor.cpython-314.pyc +0 -0
  122. package/lib/python3.14/site-packages/PIL/__pycache__/ImageDraw.cpython-314.pyc +0 -0
  123. package/lib/python3.14/site-packages/PIL/__pycache__/ImageDraw2.cpython-314.pyc +0 -0
  124. package/lib/python3.14/site-packages/PIL/__pycache__/ImageEnhance.cpython-314.pyc +0 -0
  125. package/lib/python3.14/site-packages/PIL/__pycache__/ImageFile.cpython-314.pyc +0 -0
  126. package/lib/python3.14/site-packages/PIL/__pycache__/ImageFilter.cpython-314.pyc +0 -0
  127. package/lib/python3.14/site-packages/PIL/__pycache__/ImageFont.cpython-314.pyc +0 -0
  128. package/lib/python3.14/site-packages/PIL/__pycache__/ImageGrab.cpython-314.pyc +0 -0
  129. package/lib/python3.14/site-packages/PIL/__pycache__/ImageMath.cpython-314.pyc +0 -0
  130. package/lib/python3.14/site-packages/PIL/__pycache__/ImageMode.cpython-314.pyc +0 -0
  131. package/lib/python3.14/site-packages/PIL/__pycache__/ImageMorph.cpython-314.pyc +0 -0
  132. package/lib/python3.14/site-packages/PIL/__pycache__/ImageOps.cpython-314.pyc +0 -0
  133. package/lib/python3.14/site-packages/PIL/__pycache__/ImagePalette.cpython-314.pyc +0 -0
  134. package/lib/python3.14/site-packages/PIL/__pycache__/ImagePath.cpython-314.pyc +0 -0
  135. package/lib/python3.14/site-packages/PIL/__pycache__/ImageQt.cpython-314.pyc +0 -0
  136. package/lib/python3.14/site-packages/PIL/__pycache__/ImageSequence.cpython-314.pyc +0 -0
  137. package/lib/python3.14/site-packages/PIL/__pycache__/ImageShow.cpython-314.pyc +0 -0
  138. package/lib/python3.14/site-packages/PIL/__pycache__/ImageStat.cpython-314.pyc +0 -0
  139. package/lib/python3.14/site-packages/PIL/__pycache__/ImageText.cpython-314.pyc +0 -0
  140. package/lib/python3.14/site-packages/PIL/__pycache__/ImageTk.cpython-314.pyc +0 -0
  141. package/lib/python3.14/site-packages/PIL/__pycache__/ImageTransform.cpython-314.pyc +0 -0
  142. package/lib/python3.14/site-packages/PIL/__pycache__/ImageWin.cpython-314.pyc +0 -0
  143. package/lib/python3.14/site-packages/PIL/__pycache__/ImtImagePlugin.cpython-314.pyc +0 -0
  144. package/lib/python3.14/site-packages/PIL/__pycache__/IptcImagePlugin.cpython-314.pyc +0 -0
  145. package/lib/python3.14/site-packages/PIL/__pycache__/Jpeg2KImagePlugin.cpython-314.pyc +0 -0
  146. package/lib/python3.14/site-packages/PIL/__pycache__/JpegImagePlugin.cpython-314.pyc +0 -0
  147. package/lib/python3.14/site-packages/PIL/__pycache__/JpegPresets.cpython-314.pyc +0 -0
  148. package/lib/python3.14/site-packages/PIL/__pycache__/McIdasImagePlugin.cpython-314.pyc +0 -0
  149. package/lib/python3.14/site-packages/PIL/__pycache__/MicImagePlugin.cpython-314.pyc +0 -0
  150. package/lib/python3.14/site-packages/PIL/__pycache__/MpegImagePlugin.cpython-314.pyc +0 -0
  151. package/lib/python3.14/site-packages/PIL/__pycache__/MpoImagePlugin.cpython-314.pyc +0 -0
  152. package/lib/python3.14/site-packages/PIL/__pycache__/MspImagePlugin.cpython-314.pyc +0 -0
  153. package/lib/python3.14/site-packages/PIL/__pycache__/PSDraw.cpython-314.pyc +0 -0
  154. package/lib/python3.14/site-packages/PIL/__pycache__/PaletteFile.cpython-314.pyc +0 -0
  155. package/lib/python3.14/site-packages/PIL/__pycache__/PalmImagePlugin.cpython-314.pyc +0 -0
  156. package/lib/python3.14/site-packages/PIL/__pycache__/PcdImagePlugin.cpython-314.pyc +0 -0
  157. package/lib/python3.14/site-packages/PIL/__pycache__/PcfFontFile.cpython-314.pyc +0 -0
  158. package/lib/python3.14/site-packages/PIL/__pycache__/PcxImagePlugin.cpython-314.pyc +0 -0
  159. package/lib/python3.14/site-packages/PIL/__pycache__/PdfImagePlugin.cpython-314.pyc +0 -0
  160. package/lib/python3.14/site-packages/PIL/__pycache__/PdfParser.cpython-314.pyc +0 -0
  161. package/lib/python3.14/site-packages/PIL/__pycache__/PixarImagePlugin.cpython-314.pyc +0 -0
  162. package/lib/python3.14/site-packages/PIL/__pycache__/PngImagePlugin.cpython-314.pyc +0 -0
  163. package/lib/python3.14/site-packages/PIL/__pycache__/PpmImagePlugin.cpython-314.pyc +0 -0
  164. package/lib/python3.14/site-packages/PIL/__pycache__/PsdImagePlugin.cpython-314.pyc +0 -0
  165. package/lib/python3.14/site-packages/PIL/__pycache__/QoiImagePlugin.cpython-314.pyc +0 -0
  166. package/lib/python3.14/site-packages/PIL/__pycache__/SgiImagePlugin.cpython-314.pyc +0 -0
  167. package/lib/python3.14/site-packages/PIL/__pycache__/SpiderImagePlugin.cpython-314.pyc +0 -0
  168. package/lib/python3.14/site-packages/PIL/__pycache__/SunImagePlugin.cpython-314.pyc +0 -0
  169. package/lib/python3.14/site-packages/PIL/__pycache__/TarIO.cpython-314.pyc +0 -0
  170. package/lib/python3.14/site-packages/PIL/__pycache__/TgaImagePlugin.cpython-314.pyc +0 -0
  171. package/lib/python3.14/site-packages/PIL/__pycache__/TiffImagePlugin.cpython-314.pyc +0 -0
  172. package/lib/python3.14/site-packages/PIL/__pycache__/TiffTags.cpython-314.pyc +0 -0
  173. package/lib/python3.14/site-packages/PIL/__pycache__/WalImageFile.cpython-314.pyc +0 -0
  174. package/lib/python3.14/site-packages/PIL/__pycache__/WebPImagePlugin.cpython-314.pyc +0 -0
  175. package/lib/python3.14/site-packages/PIL/__pycache__/WmfImagePlugin.cpython-314.pyc +0 -0
  176. package/lib/python3.14/site-packages/PIL/__pycache__/XVThumbImagePlugin.cpython-314.pyc +0 -0
  177. package/lib/python3.14/site-packages/PIL/__pycache__/XbmImagePlugin.cpython-314.pyc +0 -0
  178. package/lib/python3.14/site-packages/PIL/__pycache__/XpmImagePlugin.cpython-314.pyc +0 -0
  179. package/lib/python3.14/site-packages/PIL/__pycache__/__init__.cpython-314.pyc +0 -0
  180. package/lib/python3.14/site-packages/PIL/__pycache__/__main__.cpython-314.pyc +0 -0
  181. package/lib/python3.14/site-packages/PIL/__pycache__/_binary.cpython-314.pyc +0 -0
  182. package/lib/python3.14/site-packages/PIL/__pycache__/_deprecate.cpython-314.pyc +0 -0
  183. package/lib/python3.14/site-packages/PIL/__pycache__/_tkinter_finder.cpython-314.pyc +0 -0
  184. package/lib/python3.14/site-packages/PIL/__pycache__/_typing.cpython-314.pyc +0 -0
  185. package/lib/python3.14/site-packages/PIL/__pycache__/_util.cpython-314.pyc +0 -0
  186. package/lib/python3.14/site-packages/PIL/__pycache__/_version.cpython-314.pyc +0 -0
  187. package/lib/python3.14/site-packages/PIL/__pycache__/features.cpython-314.pyc +0 -0
  188. package/lib/python3.14/site-packages/PIL/__pycache__/report.cpython-314.pyc +0 -0
  189. package/lib/python3.14/site-packages/PIL/_avif.pyi +3 -0
  190. package/lib/python3.14/site-packages/PIL/_binary.py +113 -0
  191. package/lib/python3.14/site-packages/PIL/_deprecate.py +72 -0
  192. package/lib/python3.14/site-packages/PIL/_imaging.cpython-314-wasm32-wasix.so +0 -0
  193. package/lib/python3.14/site-packages/PIL/_imaging.pyi +31 -0
  194. package/lib/python3.14/site-packages/PIL/_imagingcms.pyi +143 -0
  195. package/lib/python3.14/site-packages/PIL/_imagingft.cpython-314-wasm32-wasix.so +0 -0
  196. package/lib/python3.14/site-packages/PIL/_imagingft.pyi +70 -0
  197. package/lib/python3.14/site-packages/PIL/_imagingmath.cpython-314-wasm32-wasix.so +0 -0
  198. package/lib/python3.14/site-packages/PIL/_imagingmath.pyi +3 -0
  199. package/lib/python3.14/site-packages/PIL/_imagingmorph.cpython-314-wasm32-wasix.so +0 -0
  200. package/lib/python3.14/site-packages/PIL/_imagingmorph.pyi +3 -0
  201. package/lib/python3.14/site-packages/PIL/_imagingtk.cpython-314-wasm32-wasix.so +0 -0
  202. package/lib/python3.14/site-packages/PIL/_imagingtk.pyi +3 -0
  203. package/lib/python3.14/site-packages/PIL/_tkinter_finder.py +20 -0
  204. package/lib/python3.14/site-packages/PIL/_typing.py +45 -0
  205. package/lib/python3.14/site-packages/PIL/_util.py +29 -0
  206. package/lib/python3.14/site-packages/PIL/_version.py +4 -0
  207. package/lib/python3.14/site-packages/PIL/_webp.pyi +3 -0
  208. package/lib/python3.14/site-packages/PIL/features.py +343 -0
  209. package/lib/python3.14/site-packages/PIL/py.typed +0 -0
  210. package/lib/python3.14/site-packages/PIL/report.py +5 -0
  211. package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/METADATA +180 -0
  212. package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/RECORD +117 -0
  213. package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/WHEEL +5 -0
  214. package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/licenses/LICENSE +30 -0
  215. package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/top_level.txt +1 -0
  216. package/lib/python3.14/site-packages/pillow-12.3.0.dist-info/zip-safe +1 -0
  217. 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