pyglet 2.1.13__py3-none-any.whl → 3.0.dev1__py3-none-any.whl

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 (267) hide show
  1. pyglet/__init__.py +67 -61
  2. pyglet/__init__.pyi +15 -8
  3. pyglet/app/__init__.py +22 -13
  4. pyglet/app/async_app.py +212 -0
  5. pyglet/app/base.py +2 -1
  6. pyglet/app/{xlib.py → linux.py} +3 -3
  7. pyglet/config/__init__.py +101 -0
  8. pyglet/config/gl/__init__.py +30 -0
  9. pyglet/config/gl/egl.py +120 -0
  10. pyglet/config/gl/macos.py +262 -0
  11. pyglet/config/gl/windows.py +267 -0
  12. pyglet/config/gl/x11.py +142 -0
  13. pyglet/customtypes.py +43 -2
  14. pyglet/display/__init__.py +8 -6
  15. pyglet/display/base.py +3 -63
  16. pyglet/display/cocoa.py +12 -17
  17. pyglet/display/emscripten.py +39 -0
  18. pyglet/display/headless.py +23 -30
  19. pyglet/display/wayland.py +157 -0
  20. pyglet/display/win32.py +5 -21
  21. pyglet/display/xlib.py +19 -27
  22. pyglet/display/xlib_vidmoderestore.py +2 -2
  23. pyglet/enums.py +183 -0
  24. pyglet/event.py +0 -1
  25. pyglet/experimental/geoshader_sprite.py +15 -13
  26. pyglet/experimental/hidraw.py +6 -15
  27. pyglet/experimental/multitexture_sprite.py +31 -19
  28. pyglet/experimental/particles.py +13 -35
  29. pyglet/font/__init__.py +251 -85
  30. pyglet/font/base.py +116 -61
  31. pyglet/font/dwrite/__init__.py +349 -204
  32. pyglet/font/dwrite/dwrite_lib.py +27 -5
  33. pyglet/font/fontconfig.py +14 -6
  34. pyglet/font/freetype.py +138 -87
  35. pyglet/font/freetype_lib.py +19 -0
  36. pyglet/font/group.py +179 -0
  37. pyglet/font/harfbuzz/__init__.py +3 -3
  38. pyglet/font/pyodide_js.py +310 -0
  39. pyglet/font/quartz.py +319 -126
  40. pyglet/font/ttf.py +45 -3
  41. pyglet/font/user.py +14 -19
  42. pyglet/font/win32.py +45 -21
  43. pyglet/graphics/__init__.py +8 -787
  44. pyglet/graphics/allocation.py +115 -1
  45. pyglet/graphics/api/__init__.py +77 -0
  46. pyglet/graphics/api/base.py +299 -0
  47. pyglet/graphics/api/gl/__init__.py +58 -0
  48. pyglet/graphics/api/gl/base.py +24 -0
  49. pyglet/graphics/{vertexbuffer.py → api/gl/buffer.py} +104 -159
  50. pyglet/graphics/api/gl/cocoa/context.py +76 -0
  51. pyglet/graphics/api/gl/context.py +391 -0
  52. pyglet/graphics/api/gl/default_shaders.py +0 -0
  53. pyglet/graphics/api/gl/draw.py +627 -0
  54. pyglet/graphics/api/gl/egl/__init__.py +0 -0
  55. pyglet/graphics/api/gl/egl/context.py +92 -0
  56. pyglet/graphics/api/gl/enums.py +76 -0
  57. pyglet/graphics/api/gl/framebuffer.py +315 -0
  58. pyglet/graphics/api/gl/gl.py +5463 -0
  59. pyglet/graphics/api/gl/gl_info.py +188 -0
  60. pyglet/graphics/api/gl/global_opengl.py +226 -0
  61. pyglet/{gl → graphics/api/gl}/lib.py +34 -18
  62. pyglet/graphics/api/gl/shader.py +1476 -0
  63. pyglet/graphics/api/gl/shapes.py +55 -0
  64. pyglet/graphics/api/gl/sprite.py +102 -0
  65. pyglet/graphics/api/gl/state.py +219 -0
  66. pyglet/graphics/api/gl/text.py +190 -0
  67. pyglet/graphics/api/gl/texture.py +1526 -0
  68. pyglet/graphics/{vertexarray.py → api/gl/vertexarray.py} +11 -13
  69. pyglet/graphics/api/gl/vertexdomain.py +751 -0
  70. pyglet/graphics/api/gl/win32/__init__.py +0 -0
  71. pyglet/graphics/api/gl/win32/context.py +108 -0
  72. pyglet/graphics/api/gl/win32/wgl_info.py +24 -0
  73. pyglet/graphics/api/gl/xlib/__init__.py +0 -0
  74. pyglet/graphics/api/gl/xlib/context.py +174 -0
  75. pyglet/{gl → graphics/api/gl/xlib}/glx_info.py +26 -31
  76. pyglet/graphics/api/gl1/__init__.py +0 -0
  77. pyglet/{gl → graphics/api/gl1}/gl_compat.py +3 -2
  78. pyglet/graphics/api/gl2/__init__.py +0 -0
  79. pyglet/graphics/api/gl2/buffer.py +320 -0
  80. pyglet/graphics/api/gl2/draw.py +600 -0
  81. pyglet/graphics/api/gl2/global_opengl.py +122 -0
  82. pyglet/graphics/api/gl2/shader.py +200 -0
  83. pyglet/graphics/api/gl2/shapes.py +51 -0
  84. pyglet/graphics/api/gl2/sprite.py +79 -0
  85. pyglet/graphics/api/gl2/text.py +175 -0
  86. pyglet/graphics/api/gl2/vertexdomain.py +364 -0
  87. pyglet/graphics/api/webgl/__init__.py +233 -0
  88. pyglet/graphics/api/webgl/buffer.py +302 -0
  89. pyglet/graphics/api/webgl/context.py +234 -0
  90. pyglet/graphics/api/webgl/draw.py +590 -0
  91. pyglet/graphics/api/webgl/enums.py +76 -0
  92. pyglet/graphics/api/webgl/framebuffer.py +360 -0
  93. pyglet/graphics/api/webgl/gl.py +1537 -0
  94. pyglet/graphics/api/webgl/gl_info.py +130 -0
  95. pyglet/graphics/api/webgl/shader.py +1346 -0
  96. pyglet/graphics/api/webgl/shapes.py +92 -0
  97. pyglet/graphics/api/webgl/sprite.py +102 -0
  98. pyglet/graphics/api/webgl/state.py +227 -0
  99. pyglet/graphics/api/webgl/text.py +187 -0
  100. pyglet/graphics/api/webgl/texture.py +1227 -0
  101. pyglet/graphics/api/webgl/vertexarray.py +54 -0
  102. pyglet/graphics/api/webgl/vertexdomain.py +616 -0
  103. pyglet/graphics/api/webgl/webgl_js.pyi +307 -0
  104. pyglet/{image → graphics}/atlas.py +33 -32
  105. pyglet/graphics/base.py +10 -0
  106. pyglet/graphics/buffer.py +245 -0
  107. pyglet/graphics/draw.py +578 -0
  108. pyglet/graphics/framebuffer.py +26 -0
  109. pyglet/graphics/instance.py +178 -69
  110. pyglet/graphics/shader.py +267 -1553
  111. pyglet/graphics/state.py +83 -0
  112. pyglet/graphics/texture.py +703 -0
  113. pyglet/graphics/vertexdomain.py +695 -538
  114. pyglet/gui/ninepatch.py +10 -10
  115. pyglet/gui/widgets.py +120 -10
  116. pyglet/image/__init__.py +20 -1973
  117. pyglet/image/animation.py +12 -12
  118. pyglet/image/base.py +730 -0
  119. pyglet/image/codecs/__init__.py +9 -0
  120. pyglet/image/codecs/bmp.py +53 -30
  121. pyglet/image/codecs/dds.py +53 -31
  122. pyglet/image/codecs/gdiplus.py +38 -14
  123. pyglet/image/codecs/gdkpixbuf2.py +0 -2
  124. pyglet/image/codecs/js_image.py +99 -0
  125. pyglet/image/codecs/ktx2.py +161 -0
  126. pyglet/image/codecs/pil.py +1 -1
  127. pyglet/image/codecs/png.py +1 -1
  128. pyglet/image/codecs/wic.py +11 -2
  129. pyglet/info.py +26 -24
  130. pyglet/input/__init__.py +8 -0
  131. pyglet/input/base.py +163 -105
  132. pyglet/input/controller.py +13 -19
  133. pyglet/input/controller_db.py +39 -24
  134. pyglet/input/emscripten/__init__.py +18 -0
  135. pyglet/input/emscripten/gamepad_js.py +397 -0
  136. pyglet/input/linux/__init__.py +11 -5
  137. pyglet/input/linux/evdev.py +10 -11
  138. pyglet/input/linux/x11_xinput.py +2 -2
  139. pyglet/input/linux/x11_xinput_tablet.py +1 -1
  140. pyglet/input/macos/__init__.py +7 -2
  141. pyglet/input/macos/darwin_gc.py +559 -0
  142. pyglet/input/win32/__init__.py +1 -1
  143. pyglet/input/win32/directinput.py +34 -29
  144. pyglet/input/win32/xinput.py +11 -61
  145. pyglet/lib.py +3 -3
  146. pyglet/libs/__init__.py +1 -1
  147. pyglet/{gl → libs/darwin}/agl.py +1 -1
  148. pyglet/libs/darwin/cocoapy/__init__.py +2 -2
  149. pyglet/libs/darwin/cocoapy/cocoahelpers.py +181 -0
  150. pyglet/libs/darwin/cocoapy/cocoalibs.py +31 -0
  151. pyglet/libs/darwin/cocoapy/cocoatypes.py +27 -0
  152. pyglet/libs/darwin/cocoapy/runtime.py +81 -45
  153. pyglet/libs/darwin/coreaudio.py +4 -4
  154. pyglet/{gl → libs/darwin}/lib_agl.py +9 -8
  155. pyglet/libs/darwin/quartzkey.py +1 -3
  156. pyglet/libs/egl/__init__.py +2 -0
  157. pyglet/libs/egl/egl_lib.py +576 -0
  158. pyglet/libs/egl/eglext.py +51 -5
  159. pyglet/libs/linux/__init__.py +0 -0
  160. pyglet/libs/linux/egl/__init__.py +0 -0
  161. pyglet/libs/linux/egl/eglext.py +22 -0
  162. pyglet/libs/linux/glx/__init__.py +0 -0
  163. pyglet/{gl → libs/linux/glx}/glx.py +13 -14
  164. pyglet/{gl → libs/linux/glx}/glxext_arb.py +408 -192
  165. pyglet/{gl → libs/linux/glx}/glxext_mesa.py +1 -1
  166. pyglet/{gl → libs/linux/glx}/glxext_nv.py +345 -164
  167. pyglet/{gl → libs/linux/glx}/lib_glx.py +3 -2
  168. pyglet/libs/linux/wayland/__init__.py +0 -0
  169. pyglet/libs/linux/wayland/client.py +1068 -0
  170. pyglet/libs/linux/wayland/lib_wayland.py +207 -0
  171. pyglet/libs/linux/wayland/wayland_egl.py +38 -0
  172. pyglet/libs/{wayland → linux/wayland}/xkbcommon.py +26 -0
  173. pyglet/libs/{x11 → linux/x11}/xf86vmode.py +4 -4
  174. pyglet/libs/{x11 → linux/x11}/xinerama.py +2 -2
  175. pyglet/libs/{x11 → linux/x11}/xinput.py +10 -10
  176. pyglet/libs/linux/x11/xrandr.py +0 -0
  177. pyglet/libs/{x11 → linux/x11}/xrender.py +1 -1
  178. pyglet/libs/shared/__init__.py +0 -0
  179. pyglet/libs/shared/spirv/__init__.py +0 -0
  180. pyglet/libs/shared/spirv/lib_shaderc.py +85 -0
  181. pyglet/libs/shared/spirv/lib_spirv_cross.py +126 -0
  182. pyglet/libs/win32/__init__.py +28 -8
  183. pyglet/libs/win32/constants.py +59 -48
  184. pyglet/libs/win32/context_managers.py +20 -3
  185. pyglet/libs/win32/dinput.py +105 -88
  186. pyglet/{gl → libs/win32}/lib_wgl.py +52 -26
  187. pyglet/libs/win32/types.py +58 -23
  188. pyglet/{gl → libs/win32}/wgl.py +32 -25
  189. pyglet/{gl → libs/win32}/wglext_arb.py +364 -2
  190. pyglet/media/__init__.py +9 -10
  191. pyglet/media/codecs/__init__.py +12 -1
  192. pyglet/media/codecs/base.py +99 -96
  193. pyglet/media/codecs/ffmpeg.py +2 -2
  194. pyglet/media/codecs/ffmpeg_lib/libavformat.py +3 -8
  195. pyglet/media/codecs/webaudio_pyodide.py +111 -0
  196. pyglet/media/drivers/__init__.py +9 -4
  197. pyglet/media/drivers/base.py +4 -4
  198. pyglet/media/drivers/openal/__init__.py +1 -1
  199. pyglet/media/drivers/openal/adaptation.py +3 -3
  200. pyglet/media/drivers/pulse/__init__.py +1 -1
  201. pyglet/media/drivers/pulse/adaptation.py +3 -3
  202. pyglet/media/drivers/pyodide_js/__init__.py +8 -0
  203. pyglet/media/drivers/pyodide_js/adaptation.py +288 -0
  204. pyglet/media/drivers/xaudio2/adaptation.py +3 -3
  205. pyglet/media/player.py +276 -193
  206. pyglet/media/player_worker_thread.py +1 -1
  207. pyglet/model/__init__.py +39 -29
  208. pyglet/model/codecs/base.py +4 -4
  209. pyglet/model/codecs/gltf.py +3 -3
  210. pyglet/model/codecs/obj.py +71 -43
  211. pyglet/resource.py +129 -78
  212. pyglet/shapes.py +154 -194
  213. pyglet/sprite.py +47 -164
  214. pyglet/text/__init__.py +44 -54
  215. pyglet/text/caret.py +12 -7
  216. pyglet/text/document.py +19 -17
  217. pyglet/text/formats/html.py +2 -2
  218. pyglet/text/formats/structured.py +10 -40
  219. pyglet/text/layout/__init__.py +20 -13
  220. pyglet/text/layout/base.py +176 -287
  221. pyglet/text/layout/incremental.py +9 -10
  222. pyglet/text/layout/scrolling.py +7 -95
  223. pyglet/window/__init__.py +183 -172
  224. pyglet/window/cocoa/__init__.py +62 -51
  225. pyglet/window/cocoa/pyglet_delegate.py +2 -25
  226. pyglet/window/cocoa/pyglet_view.py +9 -8
  227. pyglet/window/dialog/__init__.py +184 -0
  228. pyglet/window/dialog/base.py +99 -0
  229. pyglet/window/dialog/darwin.py +121 -0
  230. pyglet/window/dialog/linux.py +72 -0
  231. pyglet/window/dialog/windows.py +194 -0
  232. pyglet/window/emscripten/__init__.py +779 -0
  233. pyglet/window/headless/__init__.py +44 -28
  234. pyglet/window/key.py +2 -0
  235. pyglet/window/mouse.py +2 -2
  236. pyglet/window/wayland/__init__.py +377 -0
  237. pyglet/window/win32/__init__.py +101 -46
  238. pyglet/window/xlib/__init__.py +104 -66
  239. {pyglet-2.1.13.dist-info → pyglet-3.0.dev1.dist-info}/METADATA +2 -3
  240. pyglet-3.0.dev1.dist-info/RECORD +322 -0
  241. {pyglet-2.1.13.dist-info → pyglet-3.0.dev1.dist-info}/WHEEL +1 -1
  242. pyglet/gl/__init__.py +0 -208
  243. pyglet/gl/base.py +0 -499
  244. pyglet/gl/cocoa.py +0 -309
  245. pyglet/gl/gl.py +0 -4625
  246. pyglet/gl/gl.pyi +0 -2320
  247. pyglet/gl/gl_compat.pyi +0 -3097
  248. pyglet/gl/gl_info.py +0 -190
  249. pyglet/gl/headless.py +0 -166
  250. pyglet/gl/wgl_info.py +0 -36
  251. pyglet/gl/wglext_nv.py +0 -1096
  252. pyglet/gl/win32.py +0 -268
  253. pyglet/gl/xlib.py +0 -295
  254. pyglet/image/buffer.py +0 -274
  255. pyglet/image/codecs/s3tc.py +0 -354
  256. pyglet/libs/x11/xrandr.py +0 -166
  257. pyglet-2.1.13.dist-info/RECORD +0 -234
  258. /pyglet/{libs/wayland → graphics/api/gl/cocoa}/__init__.py +0 -0
  259. /pyglet/libs/{egl → linux/egl}/egl.py +0 -0
  260. /pyglet/libs/{egl → linux/egl}/lib.py +0 -0
  261. /pyglet/libs/{ioctl.py → linux/ioctl.py} +0 -0
  262. /pyglet/libs/{wayland → linux/wayland}/gbm.py +0 -0
  263. /pyglet/libs/{x11 → linux/x11}/__init__.py +0 -0
  264. /pyglet/libs/{x11 → linux/x11}/cursorfont.py +0 -0
  265. /pyglet/libs/{x11 → linux/x11}/xlib.py +0 -0
  266. /pyglet/libs/{x11 → linux/x11}/xsync.py +0 -0
  267. {pyglet-2.1.13.dist-info/licenses → pyglet-3.0.dev1.dist-info}/LICENSE +0 -0
pyglet/font/__init__.py CHANGED
@@ -15,20 +15,187 @@ pyglet will automatically load any system-installed fonts. You can add addition
15
15
  See the :mod:`pyglet.font.base` module for documentation on the base classes used
16
16
  by this package.
17
17
  """
18
+
18
19
  from __future__ import annotations
19
20
 
21
+ import collections
20
22
  import os
21
23
  import sys
22
24
  import weakref
23
- from typing import TYPE_CHECKING, BinaryIO, Iterable
25
+ from dataclasses import dataclass
26
+ from typing import TYPE_CHECKING, BinaryIO, Iterable, Sequence, Any
24
27
 
25
28
  import pyglet
26
- from pyglet import gl
29
+ from pyglet.enums import Weight, Style, Stretch
30
+ from pyglet.font.group import FontGroup
27
31
  from pyglet.font.user import UserDefinedFontBase
32
+ from pyglet.graphics.api import core
28
33
 
29
34
  if TYPE_CHECKING:
35
+ from pyglet.event import EVENT_HANDLE_STATE
30
36
  from pyglet.font.base import Font
37
+ from pyglet.graphics.api.base import SurfaceContext
38
+
39
+
40
+ @dataclass
41
+ class _FontContext:
42
+ """A container for data specific to a graphics context.
43
+
44
+ Glyph textures can only be used in their respective graphics context.
45
+ """
46
+ context: SurfaceContext
47
+ cache: weakref.WeakValueDictionary[tuple, Font]
48
+
49
+ # Hold onto refs of last three loaded fonts to prevent them being
50
+ # collected if momentarily dropped.
51
+ hold: collections.deque
52
+
53
+ def add(self, descriptor: tuple, font: Font) -> None:
54
+ """Add a font with the descriptor into the cache."""
55
+ self.cache[descriptor] = font
56
+ self.hold.appendleft(font)
57
+
58
+
59
+
60
+ class FontManager(pyglet.event.EventDispatcher):
61
+ """A manager to keep track of system fonts, font files, and user loaded fonts.
62
+
63
+ This is a global singleton and should not be instantiated by a user.
64
+
65
+ .. versionadded: 3.0
66
+ """
67
+ default_win32_font = "Segoe UI"
68
+ default_darwin_font = "System Default"
69
+ default_linux_font = "sans" # FC will convert to actual font.
70
+ default_emscripten_font = "Times New Roman"
71
+
72
+ resolved_names: dict[tuple[str, ...], str]
73
+
74
+ _font_groups: dict[str, FontGroup]
75
+ _user_font_names: set[str]
76
+ _font_contexts: weakref.WeakKeyDictionary[SurfaceContext, _FontContext]
77
+
78
+ def __init__(self) -> None:
79
+ self._font_contexts = weakref.WeakKeyDictionary()
80
+ self._hold = collections.deque(maxlen=3)
81
+ self._added_families = set()
82
+ self._added_faces = set()
83
+ self._user_font_names = set()
84
+ self._font_groups = {}
85
+
86
+ # A mapping of sequence to the resulting font name.
87
+ self.resolved_names = {}
88
+
89
+ def _invalidate(self) -> None:
90
+ """Invalidates all caches.
91
+
92
+ Used for tests.
93
+ """
94
+ self._font_contexts.clear()
95
+ self._hold.clear()
96
+ self._added_families.clear()
97
+ self._added_faces.clear()
98
+ self._user_font_names.clear()
99
+ self.resolved_names.clear()
100
+
101
+ @staticmethod
102
+ def _get_key_name(name: str | Sequence[str]) -> tuple[str, ...]:
103
+ if isinstance(name, list):
104
+ key_names = tuple(name)
105
+ elif isinstance(name, str):
106
+ key_names = (name,)
107
+ else:
108
+ key_names = name
109
+
110
+ return key_names
111
+
112
+ def get_group(self, name: str) -> FontGroup | None:
113
+ """Check if the specified name is a font group."""
114
+ return self._font_groups.get(name)
115
+
116
+ def get_resolved_name(self, name: str | Sequence[str]) -> str | None:
117
+ """Return the name of the first font found for a name or list of names.
118
+
119
+ If a font is not found, a default font will be used depending on the platform.
120
+
121
+ A sequence of font names can be used with layouts, using the first found. Each name is checked to ensure
122
+ it exists everytime it is used or text changes.
123
+
124
+ Platform specific ``have_font`` calls can be slow depending on the platform.
125
+ """
126
+ key_names = self._get_key_name(name)
127
+
128
+ if key_names in self.resolved_names:
129
+ return self.resolved_names[key_names]
130
+
131
+ for font_name in key_names:
132
+ if font_name in self._user_font_names or font_name in self._font_groups or _system_font_class.have_font(font_name):
133
+ self.resolved_names[key_names] = font_name
134
+ return font_name
135
+
136
+ # If nothing found here, then get a default name.
137
+ self.resolved_names[key_names] = manager.get_platform_default_name()
138
+ return self.resolved_names[key_names]
139
+
140
+ def get_platform_default_name(self) -> str:
141
+ """Return a font name that should be guaranteed to exist on a particular platform."""
142
+ if pyglet.compat_platform == "win32":
143
+ return self.default_win32_font
144
+ if pyglet.compat_platform == "darwin":
145
+ return self.default_darwin_font
146
+ if pyglet.compat_platform == "linux":
147
+ return self.default_linux_font
148
+ if pyglet.compat_platform == "emscripten":
149
+ return self.default_emscripten_font
150
+
151
+ msg = f"Unsupported platform: {pyglet.compat_platform}"
152
+ raise Exception(msg)
153
+
154
+ def _get_context_data(self, context: SurfaceContext | None = None) -> _FontContext:
155
+ """Get a font context based on the current graphics context."""
156
+ graphics_ctx = context or core.current_context
157
+ try:
158
+ return self._font_contexts[graphics_ctx]
159
+ except KeyError:
160
+ font_context = _FontContext(graphics_ctx, weakref.WeakValueDictionary(), collections.deque(maxlen=3))
161
+ self._font_contexts[graphics_ctx] = font_context
162
+ return font_context
163
+
164
+ def have_font(self, name: str) -> bool:
165
+ """Check if specified font name is available in the system database or user font database."""
166
+ return name in self._font_groups or name in self._user_font_names or self._get_key_name(name) in self.resolved_names or _system_font_class.have_font(name)
167
+
168
+ def _add_user_font(self, font: UserDefinedFontBase) -> None:
169
+ self._user_font_names.add(font.name)
170
+ self.dispatch_event("on_font_loaded", font.name, font.weight, font.style, font.stretch)
31
171
 
172
+ def _add_font_group(self, group: FontGroup) -> None:
173
+ self._font_groups[group.name] = group
174
+
175
+ def _add_loaded_font(self, fonts: set[tuple[str, str, str, str]]) -> None:
176
+ old = self._added_faces
177
+ new_fonts = fonts - old
178
+ self._added_faces.update(fonts)
179
+
180
+ # Clear any resolved names as this affects already loaded fonts.
181
+ self.resolved_names.clear()
182
+
183
+ for new_font in new_fonts:
184
+ self._added_families.add(new_font[0])
185
+ self.dispatch_event("on_font_loaded", *new_font)
186
+
187
+ def on_font_loaded(self, family_name: str, weight: str, style: str, stretch: str) -> EVENT_HANDLE_STATE:
188
+ """When a font is loaded, this event will be dispatched with the family name and style of the font.
189
+
190
+ On some platforms, a custom added font may not be available immediately after adding the data. In these cases,
191
+ you can set a handler on this event to get notified when it's available.
192
+
193
+ .. note:: Not currently supported by GDI.
194
+ """
195
+
196
+
197
+ manager = FontManager()
198
+ manager.register_event_type("on_font_loaded")
32
199
 
33
200
  def _get_system_font_class() -> type[Font]:
34
201
  """Get the appropriate class for the system being used.
@@ -37,22 +204,42 @@ def _get_system_font_class() -> type[Font]:
37
204
  """
38
205
  if pyglet.compat_platform == "darwin":
39
206
  from pyglet.font.quartz import QuartzFont
207
+
40
208
  _font_class = QuartzFont
41
209
 
42
210
  elif pyglet.compat_platform in ("win32", "cygwin"):
43
211
  from pyglet.libs.win32.constants import WINDOWS_7_OR_GREATER
212
+
44
213
  if WINDOWS_7_OR_GREATER and not pyglet.options["win32_gdi_font"]:
45
214
  from pyglet.font.dwrite import Win32DirectWriteFont
215
+
46
216
  _font_class = Win32DirectWriteFont
47
217
  else:
48
218
  from pyglet.font.win32 import GDIPlusFont
219
+
49
220
  _font_class = GDIPlusFont
50
- else:
221
+ elif pyglet.compat_platform == "linux":
51
222
  from pyglet.font.freetype import FreeTypeFont
223
+
52
224
  _font_class = FreeTypeFont
225
+ elif pyglet.compat_platform == "emscripten":
226
+ from pyglet.font.pyodide_js import JavascriptPyodideFont
227
+
228
+ _font_class = JavascriptPyodideFont
229
+ else:
230
+ raise Exception("Font Renderer is not available for this Operating System.")
53
231
 
54
232
  return _font_class
55
233
 
234
+ def add_group(font_group: FontGroup) -> None:
235
+ """Add a font group to pyglet's list of font groups."""
236
+ assert isinstance(font_group, FontGroup), "Added group must be based on a FontGroup class."
237
+
238
+ if _system_font_class.have_font(font_group.name):
239
+ msg = f"Cannot use FontGroup, name '{font_group.name}' already exists within the system or loaded fonts."
240
+ raise Exception(msg)
241
+
242
+ manager._add_font_group(font_group) # noqa: SLF001
56
243
 
57
244
  def add_user_font(font: UserDefinedFontBase) -> None:
58
245
  """Add a custom font created by the user.
@@ -72,126 +259,98 @@ def add_user_font(font: UserDefinedFontBase) -> None:
72
259
  raise Exception(msg)
73
260
 
74
261
  # Locate or create font cache
75
- shared_object_space = gl.current_context.object_space
76
- if not hasattr(shared_object_space, "pyglet_font_font_cache"):
77
- shared_object_space.pyglet_font_font_cache = weakref.WeakValueDictionary()
78
- shared_object_space.pyglet_font_font_hold = []
79
- # Match a tuple to specific name to reduce lookups.
80
- shared_object_space.pyglet_font_font_name_match = {}
81
- font_cache = shared_object_space.pyglet_font_font_cache
82
- font_hold = shared_object_space.pyglet_font_font_hold
262
+ font_context = manager._get_context_data(core.current_context) # noqa: SLF001
83
263
 
84
264
  # Look for font name in font cache
85
- descriptor = (font.name, font.size, font.weight, font.italic, font.stretch, font.dpi)
86
- if descriptor in font_cache:
87
- msg = f"A font with parameters {descriptor} has already been created."
265
+ descriptor = (font.name, font.size, font.weight, font.style, font.stretch, font.dpi)
266
+ if descriptor in font_context.cache:
267
+ msg = f"A font with parameters {descriptor} has already been created. Use a more unique name."
88
268
  raise Exception(msg)
89
269
  if _system_font_class.have_font(font.name):
90
- msg = f"Font name '{font.name}' already exists within the system fonts."
270
+ msg = f"Font name '{font.name}' already exists within the system or loaded fonts."
91
271
  raise Exception(msg)
92
272
 
93
- if font.name not in _user_fonts:
94
- _user_fonts.append(font.name)
95
- # Cache font in weak-ref dictionary to avoid reloading while still in use
96
- font_cache[descriptor] = font
97
- # Hold onto refs of last three loaded fonts to prevent them being
98
- # collected if momentarily dropped.
99
- del font_hold[3:]
100
- font_hold.insert(0, font)
273
+ font_context.add(descriptor, font)
274
+ manager._add_user_font(font) # noqa: SLF001
101
275
 
102
276
 
103
277
  def have_font(name: str) -> bool:
104
278
  """Check if specified font name is available in the system database or user font database."""
105
- return name in _user_fonts or _system_font_class.have_font(name)
279
+ return manager.have_font(name)
106
280
 
107
281
 
108
- def load(name: str | Iterable[str] | None = None, size: float | None = None, weight: str = "normal",
109
- italic: bool | str = False, stretch: bool | str = False, dpi: int | None = None) -> Font:
282
+ def load(
283
+ name: str | Iterable[str] | None = None,
284
+ size: float | None = None,
285
+ weight: str | None = "normal",
286
+ style: str | None = "normal",
287
+ stretch: str | None = "normal",
288
+ dpi: int | None = None,
289
+ ) -> Font:
110
290
  """Load a font for rendering.
111
291
 
112
292
  Args:
113
293
  name:
114
294
  Font family, for example, "Times New Roman". If a list of names
115
295
  is provided, the first one matching a known font is used. If no
116
- font can be matched to the name(s), a default font is used. The default font
117
- will be platform dependent.
296
+ font can be matched to the name(s), a default font is used.
297
+ The default font will be platform dependent.
118
298
  size:
119
299
  Size of the font, in points. The returned font may be an exact
120
300
  match or the closest available.
121
301
  weight:
122
302
  If set, a specific weight variant is returned if one exists for the given font
123
- family and size. The weight is provided as a string. For example: "bold" or "light".
124
- italic:
125
- If True, an italic variant is returned, if one exists for the given family and size. For some Font
126
- renderers, italics may have an "oblique" variation which can be specified as a string.
303
+ family and size. For example, "bold" can be specified. Refer to :py:class:`~pyglet.enums.Weight`
304
+ for valid options.
305
+ style:
306
+ If specified, an italic variant can be returned if one exists for the given family and size. If a font is
307
+ oblique, or italic, either will fallback to choose that variation. Refer to :py:class:`~pyglet.enums.Style`
308
+ for valid options.
127
309
  stretch:
128
- If True, a stretch variant is returned, if one exists for the given family and size. Currently only
129
- supported by Windows through the ``DirectWrite`` font renderer. For example, "condensed" or "expanded".
310
+ If specified a stretch variant is returned, if one exists for the given family and size. Refer to
311
+ :py:class:`~pyglet.enums.Stretch` for valid options.
130
312
  dpi: int
131
313
  The assumed resolution of the display device, for the purposes of
132
314
  determining the pixel size of the font. Defaults to 96.
133
315
  """
134
- # Arbitrary default size
135
- if size is None:
136
- size = 12
137
- if dpi is None:
138
- dpi = 96
316
+ # TextLayouts pass `None` for unused style keys from run _FontStyleRunsRangeIterator.
317
+ size = size or 12 # Arbitrary default size
318
+ dpi = dpi or 96
319
+ weight = weight or Weight.NORMAL
320
+ style = style or Style.NORMAL
321
+ stretch = stretch or Stretch.NORMAL
139
322
 
140
- # Locate or create font cache
141
- shared_object_space = gl.current_context.object_space
142
- if not hasattr(shared_object_space, "pyglet_font_font_cache"):
143
- shared_object_space.pyglet_font_font_cache = weakref.WeakValueDictionary()
144
- shared_object_space.pyglet_font_font_hold = []
145
- # Match a tuple to specific name to reduce lookups.
146
- shared_object_space.pyglet_font_font_name_match = {}
147
- font_cache = shared_object_space.pyglet_font_font_cache
148
- font_hold = shared_object_space.pyglet_font_font_hold
149
- font_name_match = shared_object_space.pyglet_font_font_name_match
150
-
151
- if isinstance(name, (tuple, list)):
152
- if isinstance(name, list):
153
- name = tuple(name)
154
- if name in font_name_match:
155
- name = font_name_match[name]
156
- else:
157
- # Find first matching name, cache it.
158
- found_name = None
159
- for n in name:
160
- if n in _user_fonts or _system_font_class.have_font(n):
161
- found_name = n
162
- break
323
+ if name is None:
324
+ name = manager.get_platform_default_name()
163
325
 
164
- font_name_match[name] = found_name
165
- name = found_name
326
+ font_context = manager._get_context_data(core.current_context) # noqa: SLF001
327
+ name = manager.get_resolved_name(name)
166
328
 
167
329
  # Look for font name in font cache
168
- descriptor = (name, size, weight, italic, stretch, dpi)
169
- if descriptor in font_cache:
170
- return font_cache[descriptor]
171
-
172
- # Not in cache, create from scratch
173
- font = _system_font_class(name, size, weight=weight, italic=italic, stretch=stretch, dpi=dpi)
330
+ descriptor = (name, size, weight, style, stretch, dpi)
331
+ if descriptor in font_context.cache:
332
+ return font_context.cache[descriptor]
333
+
334
+ assert weight is not None
335
+ assert style is not None
336
+ assert stretch is not None
337
+ if font_group := manager.get_group(name):
338
+ font = font_group.get_font(size, weight=weight, style=style, stretch=stretch, dpi=dpi)
339
+ else:
340
+ font = _system_font_class(name, size, weight=weight, style=style, stretch=stretch, dpi=dpi)
174
341
 
175
- # Save parameters for new-style layout classes to recover
176
- # TODO: add properties to the base Font so completion is proper:
177
- font.size = size
178
- font.weight = weight
179
- font.italic = italic
180
- font.stretch = stretch
181
- font.dpi = dpi
342
+ # Font system changed the name. Create two descriptors for it, so both can be used.
343
+ if font.name != name:
344
+ fs_descriptor = (font.name, size, weight, style, stretch, dpi)
345
+ font_context.add(fs_descriptor, font)
182
346
 
183
347
  # Cache font in weak-ref dictionary to avoid reloading while still in use
184
- font_cache[descriptor] = font
185
- # Hold onto refs of last three loaded fonts to prevent them being
186
- # collected if momentarily dropped.
187
- del font_hold[3:]
188
- font_hold.insert(0, font)
348
+ font_context.add(descriptor, font)
189
349
  return font
190
350
 
191
351
 
192
352
  if not getattr(sys, "is_pyglet_doc_run", False):
193
353
  _system_font_class = _get_system_font_class()
194
- _user_fonts = []
195
354
 
196
355
 
197
356
  def add_file(font: str | BinaryIO | bytes) -> None:
@@ -215,7 +374,7 @@ def add_file(font: str | BinaryIO | bytes) -> None:
215
374
  font = open(font, "rb") # noqa: SIM115
216
375
  if hasattr(font, "read"):
217
376
  font = font.read()
218
- _system_font_class.add_font_data(font)
377
+ _system_font_class.add_font_data(font, manager)
219
378
 
220
379
 
221
380
  def add_directory(directory: str) -> None:
@@ -233,5 +392,12 @@ def add_directory(directory: str) -> None:
233
392
  if file[-4:].lower() == ".ttf":
234
393
  add_file(os.path.join(directory, file))
235
394
 
395
+ def get_custom_font_names() -> tuple[str, ...]:
396
+ """The names of font families added to pyglet via :py:func:`~pyglet.font.add_file`.
397
+
398
+ .. versionadded:: 3.0
399
+ """
400
+ return tuple(manager._added_families) # noqa: SLF001
401
+
236
402
 
237
- __all__ = ("add_file", "add_directory", "add_user_font", "load", "have_font")
403
+ __all__ = ("add_directory", "add_file", "add_user_font", "get_custom_font_names", "have_font", "load", "manager")