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
@@ -1,14 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import contextlib
3
4
  import math
4
5
  import pathlib
5
6
  from ctypes import POINTER, Array, byref, c_void_p, cast, create_unicode_buffer, pointer, py_object, sizeof, string_at
6
7
  from ctypes.wintypes import BOOL, FLOAT, UINT
7
8
  from enum import Flag
8
- from typing import TYPE_CHECKING, BinaryIO, Sequence
9
+ from typing import TYPE_CHECKING, BinaryIO, Sequence, Generator
9
10
 
10
11
  import pyglet
11
- from pyglet.font import base
12
+ from pyglet.font import base, FontManager
12
13
  from pyglet.font.base import Glyph, GlyphPosition
13
14
  from pyglet.font.dwrite.d2d1_lib import (
14
15
  D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT,
@@ -104,6 +105,7 @@ from pyglet.font.dwrite.dwrite_lib import (
104
105
  IID_IDWriteLocalFontFileLoader,
105
106
  LegacyCollectionLoader,
106
107
  LegacyFontFileLoader,
108
+ DWRITE_INFORMATIONAL_STRING_FULL_NAME,
107
109
  )
108
110
  from pyglet.font.harfbuzz import get_harfbuzz_shaped_glyphs, get_resource_from_dw_font, harfbuzz_available
109
111
  from pyglet.image.codecs.wincodec_lib import GUID_WICPixelFormat32bppPBGRA
@@ -132,9 +134,9 @@ except ImportError as err:
132
134
 
133
135
 
134
136
  name_to_weight = {
135
- True: DWRITE_FONT_WEIGHT_BOLD, # Temporary alias for attributed text
136
- False: DWRITE_FONT_WEIGHT_NORMAL, # Temporary alias for attributed text
137
- None: DWRITE_FONT_WEIGHT_NORMAL, # Temporary alias for attributed text
137
+ True: DWRITE_FONT_WEIGHT_BOLD, # Temporary alias for attributed text
138
+ False: DWRITE_FONT_WEIGHT_NORMAL, # Temporary alias for attributed text
139
+ None: DWRITE_FONT_WEIGHT_NORMAL, # Temporary alias for attributed text
138
140
  "thin": DWRITE_FONT_WEIGHT_THIN,
139
141
  "extralight": DWRITE_FONT_WEIGHT_EXTRA_LIGHT,
140
142
  "ultralight": DWRITE_FONT_WEIGHT_ULTRA_LIGHT,
@@ -152,6 +154,26 @@ name_to_weight = {
152
154
  "heavy": DWRITE_FONT_WEIGHT_HEAVY,
153
155
  "extrablack": DWRITE_FONT_WEIGHT_EXTRA_BLACK,
154
156
  }
157
+
158
+ weight_to_name = {
159
+ DWRITE_FONT_WEIGHT_BOLD: "bold",
160
+ DWRITE_FONT_WEIGHT_NORMAL: "normal",
161
+ DWRITE_FONT_WEIGHT_THIN: "thin",
162
+ DWRITE_FONT_WEIGHT_EXTRA_LIGHT: "extralight",
163
+ DWRITE_FONT_WEIGHT_ULTRA_LIGHT: "ultralight",
164
+ DWRITE_FONT_WEIGHT_LIGHT: "light",
165
+ DWRITE_FONT_WEIGHT_SEMI_LIGHT: "semilight",
166
+ DWRITE_FONT_WEIGHT_REGULAR: "regular",
167
+ DWRITE_FONT_WEIGHT_MEDIUM: "medium",
168
+ DWRITE_FONT_WEIGHT_DEMI_BOLD: "demibold",
169
+ DWRITE_FONT_WEIGHT_SEMI_BOLD: "semibold",
170
+ DWRITE_FONT_WEIGHT_EXTRA_BOLD: "extrabold",
171
+ DWRITE_FONT_WEIGHT_ULTRA_BOLD: "ultrabold",
172
+ DWRITE_FONT_WEIGHT_BLACK: "black",
173
+ DWRITE_FONT_WEIGHT_HEAVY: "heavy",
174
+ DWRITE_FONT_WEIGHT_EXTRA_BLACK: "extrablack",
175
+ }
176
+
155
177
  name_to_stretch = {
156
178
  "undefined": DWRITE_FONT_STRETCH_UNDEFINED,
157
179
  "ultracondensed": DWRITE_FONT_STRETCH_ULTRA_CONDENSED,
@@ -165,12 +187,27 @@ name_to_stretch = {
165
187
  "extraexpanded": DWRITE_FONT_STRETCH_EXTRA_EXPANDED,
166
188
  "narrow": DWRITE_FONT_STRETCH_CONDENSED,
167
189
  }
190
+ stretch_to_name = {
191
+ DWRITE_FONT_STRETCH_UNDEFINED: "undefined",
192
+ DWRITE_FONT_STRETCH_ULTRA_CONDENSED: "ultracondensed",
193
+ DWRITE_FONT_STRETCH_EXTRA_CONDENSED: "extracondensed",
194
+ DWRITE_FONT_STRETCH_CONDENSED: "condensed",
195
+ DWRITE_FONT_STRETCH_SEMI_CONDENSED: "semicondensed",
196
+ DWRITE_FONT_STRETCH_NORMAL: "normal",
197
+ DWRITE_FONT_STRETCH_SEMI_EXPANDED: "semiexpanded",
198
+ DWRITE_FONT_STRETCH_EXPANDED: "expanded",
199
+ DWRITE_FONT_STRETCH_EXTRA_EXPANDED: "extraexpanded",
200
+ }
168
201
  name_to_style = {
169
202
  "normal": DWRITE_FONT_STYLE_NORMAL,
170
203
  "oblique": DWRITE_FONT_STYLE_OBLIQUE,
171
204
  "italic": DWRITE_FONT_STYLE_ITALIC,
172
205
  }
173
-
206
+ style_to_name = {
207
+ DWRITE_FONT_STYLE_NORMAL: "normal",
208
+ DWRITE_FONT_STYLE_OBLIQUE: "oblique",
209
+ DWRITE_FONT_STYLE_ITALIC: "italic",
210
+ }
174
211
 
175
212
  class DWRITE_GLYPH_IMAGE_FORMAT_FLAG(Flag):
176
213
  NONE = 0x00000000
@@ -185,7 +222,6 @@ class DWRITE_GLYPH_IMAGE_FORMAT_FLAG(Flag):
185
222
  COLR_PAINT_TREE = 0x00000100
186
223
 
187
224
 
188
-
189
225
  def get_system_locale() -> str:
190
226
  """Retrieve the string representing the system locale."""
191
227
  local_name = create_unicode_buffer(LOCALE_NAME_MAX_LENGTH)
@@ -199,6 +235,7 @@ class _DWriteTextRenderer(com.COMObject):
199
235
  This allows the use of DirectWrite shaping to offload manual shaping, fallback detection, glyph combining, and
200
236
  other complicated scenarios.
201
237
  """
238
+
202
239
  _interfaces_ = [IDWriteTextRenderer] # noqa: RUF012
203
240
 
204
241
  def __init__(self) -> None:
@@ -217,7 +254,7 @@ class _DWriteTextRenderer(com.COMObject):
217
254
  def DrawUnderline(self, *_args) -> int: # noqa: ANN002, N802
218
255
  return com.E_NOTIMPL
219
256
 
220
- def DrawStrikethrough(self, *_args)-> int: # noqa: ANN002, N802
257
+ def DrawStrikethrough(self, *_args) -> int: # noqa: ANN002, N802
221
258
  return com.E_NOTIMPL
222
259
 
223
260
  def DrawInlineObject(self, *_args) -> int: # noqa: ANN002, N802
@@ -235,15 +272,19 @@ class _DWriteTextRenderer(com.COMObject):
235
272
  transform[0] = self.dmatrix
236
273
  return 0
237
274
 
238
- def DrawGlyphRun(self, drawing_context: c_void_p, # noqa: N802
239
- _baseline_x: float, _baseline_y: float, mode: int,
240
- glyph_run_ptr: POINTER(DWRITE_GLYPH_RUN),
241
- _run_des: POINTER(DWRITE_GLYPH_RUN_DESCRIPTION),
242
- _effect: c_void_p) -> int:
243
-
275
+ def DrawGlyphRun(
276
+ self,
277
+ drawing_context: c_void_p,
278
+ _baseline_x: float,
279
+ _baseline_y: float,
280
+ mode: int,
281
+ glyph_run_ptr: POINTER(DWRITE_GLYPH_RUN),
282
+ _run_des: POINTER(DWRITE_GLYPH_RUN_DESCRIPTION),
283
+ _effect: c_void_p,
284
+ ) -> int:
244
285
  c_buf = create_unicode_buffer(_run_des.contents.text)
245
286
 
246
- c_wchar_txt = c_buf[:_run_des.contents.textLength]
287
+ c_wchar_txt = c_buf[: _run_des.contents.textLength]
247
288
  pystr_len = len(c_wchar_txt)
248
289
 
249
290
  glyph_renderer: DirectWriteGlyphRenderer = cast(drawing_context, py_object).value
@@ -282,10 +323,10 @@ class _DWriteTextRenderer(com.COMObject):
282
323
  # In some cases (italics) the offsets may be NULL.
283
324
  if glyph_run.glyphOffsets:
284
325
  offset = base.GlyphPosition(
285
- (glyph_run.glyphAdvances[i] - glyph.advance),
286
- 0,
287
- glyph_run.glyphOffsets[i].advanceOffset,
288
- glyph_run.glyphOffsets[i].ascenderOffset,
326
+ (glyph_run.glyphAdvances[i] - glyph.advance),
327
+ 0,
328
+ glyph_run.glyphOffsets[i].advanceOffset,
329
+ glyph_run.glyphOffsets[i].ascenderOffset,
289
330
  )
290
331
 
291
332
  else:
@@ -306,35 +347,42 @@ class _DWriteTextRenderer(com.COMObject):
306
347
 
307
348
  _renderer = _DWriteTextRenderer()
308
349
 
309
- def get_glyph_metrics(font_face: IDWriteFontFace, indices: Array[UINT16], count: int) -> list[
310
- tuple[float, float, float, float, float]]:
311
- """Obtain metrics for the specific string.
312
350
 
313
- Returns:
314
- A list of tuples with the following metrics per indice:
315
- . (glyph width, glyph height, left side bearing, advance width, bottom side bearing)
316
- """
317
- glyph_metrics = (DWRITE_GLYPH_METRICS * count)()
318
- font_face.GetDesignGlyphMetrics(indices, count, glyph_metrics, False)
351
+ def get_glyph_metrics(
352
+ font_face: IDWriteFontFace, indices: Array[UINT16], count: int,
353
+ ) -> list[tuple[float, float, float, float, float]]:
354
+ """Obtain metrics for the specific string.
355
+
356
+ Returns:
357
+ A list of tuples with the following metrics per indice:
358
+ . (glyph width, glyph height, left side bearing, advance width, bottom side bearing)
359
+ """
360
+ glyph_metrics = (DWRITE_GLYPH_METRICS * count)()
361
+ font_face.GetDesignGlyphMetrics(indices, count, glyph_metrics, False)
319
362
 
320
- metrics_out = []
321
- for metric in glyph_metrics:
322
- glyph_width = (metric.advanceWidth + abs(metric.leftSideBearing) + abs(metric.rightSideBearing))
323
- glyph_height = (metric.advanceHeight - metric.topSideBearing - metric.bottomSideBearing)
363
+ metrics_out = []
364
+ for metric in glyph_metrics:
365
+ glyph_width = metric.advanceWidth + abs(metric.leftSideBearing) + abs(metric.rightSideBearing)
366
+ glyph_height = metric.advanceHeight - metric.topSideBearing - metric.bottomSideBearing
324
367
 
325
- lsb = metric.leftSideBearing
326
- bsb = metric.bottomSideBearing
368
+ lsb = metric.leftSideBearing
369
+ bsb = metric.bottomSideBearing
327
370
 
328
- advance_width = metric.advanceWidth
371
+ advance_width = metric.advanceWidth
329
372
 
330
- metrics_out.append((glyph_width, glyph_height, lsb, advance_width, bsb))
373
+ metrics_out.append((glyph_width, glyph_height, lsb, advance_width, bsb))
374
+
375
+ return metrics_out
331
376
 
332
- return metrics_out
333
377
 
334
378
  class DirectWriteGlyphRenderer(base.GlyphRenderer): # noqa: D101
335
379
  current_run: list[DWRITE_GLYPH_RUN]
336
380
  font: Win32DirectWriteFont
337
- antialias_mode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT if pyglet.options.text_antialiasing is True else D2D1_TEXT_ANTIALIAS_MODE_ALIASED
381
+ antialias_mode = (
382
+ D2D1_TEXT_ANTIALIAS_MODE_DEFAULT
383
+ if pyglet.options.text_antialiasing is True
384
+ else D2D1_TEXT_ANTIALIAS_MODE_ALIASED
385
+ )
338
386
  draw_options = D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT if WINDOWS_8_1_OR_GREATER else D2D1_DRAW_TEXT_OPTIONS_NONE
339
387
  measuring_mode = DWRITE_MEASURING_MODE_NATURAL
340
388
 
@@ -391,10 +439,7 @@ class DirectWriteGlyphRenderer(base.GlyphRenderer): # noqa: D101
391
439
 
392
440
  rt.Clear(transparent)
393
441
 
394
- rt.DrawTextLayout(no_offset,
395
- text_layout,
396
- self._brush,
397
- self.draw_options)
442
+ rt.DrawTextLayout(no_offset, text_layout, self._brush, self.draw_options)
398
443
 
399
444
  rt.EndDraw(None, None)
400
445
 
@@ -402,10 +447,9 @@ class DirectWriteGlyphRenderer(base.GlyphRenderer): # noqa: D101
402
447
 
403
448
  return wic.extract_image_data(bitmap, wic_fmt)
404
449
 
405
- def render_single_glyph(self, font_face: IDWriteFontFace,
406
- indice: int,
407
- metrics: tuple[float, float, float, float, float],
408
- mode: int) -> base.Glyph:
450
+ def render_single_glyph(
451
+ self, font_face: IDWriteFontFace, indice: int, metrics: tuple[float, float, float, float, float], mode: int,
452
+ ) -> base.Glyph:
409
453
  """Renders a single glyph indice using Direct2D."""
410
454
  glyph_width, glyph_height, glyph_lsb, glyph_advance, glyph_bsb = metrics
411
455
 
@@ -451,8 +495,7 @@ class DirectWriteGlyphRenderer(base.GlyphRenderer): # noqa: D101
451
495
  self._create_bitmap(render_width, render_height)
452
496
 
453
497
  # Glyphs are drawn at the baseline, and with LSB, so we need to offset it based on top left position.
454
- baseline_offset = D2D_POINT_2F(-render_offset_x,
455
- self.font.ascent)
498
+ baseline_offset = D2D_POINT_2F(-render_offset_x, self.font.ascent)
456
499
 
457
500
  self._render_target.BeginDraw()
458
501
 
@@ -499,21 +542,15 @@ class DirectWriteGlyphRenderer(base.GlyphRenderer): # noqa: D101
499
542
  baseline_offset,
500
543
  color_run.contents.glyphRun,
501
544
  self.measuring_mode,
502
- )
545
+ )
503
546
  else:
504
547
  glyph_run = color_run.contents.glyphRun
505
- self._render_target.DrawGlyphRun(baseline_offset,
506
- glyph_run,
507
- brush,
508
- mode)
548
+ self._render_target.DrawGlyphRun(baseline_offset, glyph_run, brush, mode)
509
549
  enumerator.Release()
510
550
  if temp_brush:
511
551
  temp_brush.Release()
512
552
  else:
513
- self._render_target.DrawGlyphRun(baseline_offset,
514
- run,
515
- self._brush,
516
- mode)
553
+ self._render_target.DrawGlyphRun(baseline_offset, run, self._brush, mode)
517
554
 
518
555
  self._render_target.EndDraw(None, None)
519
556
 
@@ -521,12 +558,13 @@ class DirectWriteGlyphRenderer(base.GlyphRenderer): # noqa: D101
521
558
 
522
559
  glyph = self.font.create_glyph(image)
523
560
 
524
- glyph.set_bearings(-self.font.descent, render_offset_x,
525
- glyph_advance * self.font.font_scale_ratio)
561
+ glyph.set_bearings(-self.font.descent, render_offset_x, glyph_advance * self.font.font_scale_ratio)
526
562
 
527
563
  return glyph
528
564
 
529
- def _get_color_enumerator(self, dwrite_run: DWRITE_GLYPH_RUN) -> IDWriteColorGlyphRunEnumerator | IDWriteColorGlyphRunEnumerator1 | None:
565
+ def _get_color_enumerator(
566
+ self, dwrite_run: DWRITE_GLYPH_RUN,
567
+ ) -> IDWriteColorGlyphRunEnumerator | IDWriteColorGlyphRunEnumerator1 | None:
530
568
  """Obtain a color enumerator if possible."""
531
569
  try:
532
570
  if WINDOWS_10_CREATORS_UPDATE_OR_GREATER:
@@ -545,7 +583,8 @@ class DirectWriteGlyphRenderer(base.GlyphRenderer): # noqa: D101
545
583
  elif WINDOWS_8_1_OR_GREATER:
546
584
  enumerator = IDWriteColorGlyphRunEnumerator()
547
585
  self.font._write_factory.TranslateColorGlyphRun(
548
- 0.0, 0.0,
586
+ 0.0,
587
+ 0.0,
549
588
  dwrite_run,
550
589
  None,
551
590
  self.measuring_mode,
@@ -590,10 +629,7 @@ class DirectWriteGlyphRenderer(base.GlyphRenderer): # noqa: D101
590
629
 
591
630
  self._render_target.Clear(transparent)
592
631
 
593
- self._render_target.DrawTextLayout(point,
594
- text_layout,
595
- self._brush,
596
- self.draw_options)
632
+ self._render_target.DrawTextLayout(point, text_layout, self._brush, self.draw_options)
597
633
 
598
634
  self._render_target.EndDraw(None, None)
599
635
 
@@ -646,9 +682,10 @@ def _get_font_file(font_face: IDWriteFontFace) -> IDWriteFontFile:
646
682
  font_files = (IDWriteFontFile * file_ct.value)()
647
683
  font_face.GetFiles(byref(file_ct), font_files)
648
684
 
649
- return font_files[0]
685
+ return font_files[font_face.GetIndex()]
650
686
 
651
- def _get_font_ref(font_file: IDWriteFontFile, release_file: bool=True) -> tuple[c_void_p, int]:
687
+
688
+ def _get_font_ref(font_file: IDWriteFontFile, release_file: bool = True) -> tuple[c_void_p, int]:
652
689
  """Get a unique font reference for the font face.
653
690
 
654
691
  Callbacks will generate new addresses for the same IDWriteFontFace, so a unique value
@@ -669,9 +706,152 @@ def _get_font_ref(font_file: IDWriteFontFile, release_file: bool=True) -> tuple[
669
706
 
670
707
  return key_data, ff_key_size.value
671
708
 
709
+ def _get_font_face_names(font_family: IDWriteFontFamily, locale: str) -> set[str]:
710
+ face_names = set()
711
+ ct = font_family.GetFontCount()
712
+ for i in range(ct):
713
+ temp_ft = IDWriteFont()
714
+ font_family.GetFont(i, byref(temp_ft))
715
+
716
+ # When a font is loaded, it may not include things like bold or italic. These get simulated
717
+ # by the font renderer. Ignore those and only utilize those not actually simulated.
718
+ if temp_ft.GetSimulations() == 0:
719
+ exists = BOOL()
720
+ lstring = IDWriteLocalizedStrings()
721
+
722
+ temp_ft.GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_FULL_NAME, byref(lstring), byref(exists))
723
+
724
+ if exists.value:
725
+ full_name = ''.join(_unpack_localized_string(lstring, locale))
726
+
727
+ weight_name = weight_to_name.get(temp_ft.GetWeight(), "normal")
728
+ style_name = style_to_name.get(temp_ft.GetStyle(), "normal")
729
+ stretch_name = stretch_to_name.get(temp_ft.GetStretch(), "normal")
730
+
731
+ face_names.add((full_name, weight_name, style_name, stretch_name))
732
+
733
+ temp_ft.Release()
734
+
735
+ return face_names
736
+
737
+ def _get_font_families(collection: IDWriteFontCollection) -> Generator[IDWriteFontFamily, None, None]:
738
+ """Generate IDWriteFontFamily objects associated with this collection."""
739
+ ct = collection.GetFontFamilyCount()
740
+
741
+ for i in range(ct):
742
+ family = IDWriteFontFamily()
743
+ collection.GetFontFamily(i, byref(family))
744
+ try:
745
+ yield family
746
+ finally:
747
+ family.Release()
748
+
749
+ def _get_font_collection_family_names(collection: IDWriteFontCollection) -> set[str]:
750
+ """Retrieve the family names of font's from the font collection."""
751
+ locale = get_system_locale()
752
+
753
+ names = set()
754
+
755
+ for family in _get_font_families(collection):
756
+ family_name_str = IDWriteLocalizedStrings()
757
+ family.GetFamilyNames(byref(family_name_str))
758
+ family_names = _unpack_localized_string(family_name_str, locale)
759
+
760
+ names.update(family_names)
761
+
762
+ return names
763
+
764
+ def _get_font_collection_family_data(collection: IDWriteFontCollection) -> set[str]:
765
+ """Retrieve font data information associated with this collection.
766
+
767
+ The function will iterate over every font family, and then the faces of that family.
768
+ """
769
+ locale = get_system_locale()
770
+
771
+ data = set()
772
+
773
+ for family in _get_font_families(collection):
774
+ family_name_str = IDWriteLocalizedStrings()
775
+ family.GetFamilyNames(byref(family_name_str))
776
+ family_name = ''.join(_unpack_localized_string(family_name_str, locale))
777
+
778
+ face_data = _get_font_face_names(family, locale)
779
+
780
+ family_data = [(family_name, weight, italic, stretch) for (_, weight, italic, stretch) in face_data]
781
+
782
+ data.update(family_data)
783
+
784
+ return data
785
+
786
+ def _get_font_collection_face_names(collection: IDWriteFontCollection) -> set[tuple[str, str, str, str]]:
787
+ """Retrieve the font face names of all font's from the collection.
788
+
789
+ The function will iterate over every font family, and then the faces of that family.
790
+
791
+ Returns:
792
+ A set of tuples containing: the family name, the weight, the style, and the stretch.
793
+ """
794
+ locale = get_system_locale()
795
+
796
+ names = set()
797
+
798
+ for family in _get_font_families(collection):
799
+ family_name_str = IDWriteLocalizedStrings()
800
+ family.GetFamilyNames(byref(family_name_str))
801
+ face_names = _get_font_face_names(family, locale)
802
+ names.update(face_names)
803
+
804
+ return names
805
+
806
+ def _get_localized_index(strings: IDWriteLocalizedStrings, locale: str) -> int:
807
+ idx = UINT32()
808
+ exists = BOOL()
809
+
810
+ if locale:
811
+ strings.FindLocaleName(locale, byref(idx), byref(exists))
812
+
813
+ if not exists.value:
814
+ # fallback to english.
815
+ strings.FindLocaleName("en-us", byref(idx), byref(exists))
816
+
817
+ if not exists:
818
+ return 0
819
+
820
+ return idx.value
821
+
822
+ return 0
823
+
824
+ def _unpack_localized_string(local_string: IDWriteLocalizedStrings, locale: str) -> list[str]:
825
+ """Takes IDWriteLocalizedStrings and unpacks the strings inside of it into a list.
826
+
827
+ ..note:: local_string is released before this function returns.
828
+ """
829
+ str_array_len = local_string.GetCount()
830
+
831
+ strings = []
832
+ for _ in range(str_array_len):
833
+ string_size = UINT32()
834
+
835
+ idx = _get_localized_index(local_string, locale)
836
+
837
+ local_string.GetStringLength(idx, byref(string_size))
838
+
839
+ buffer_size = string_size.value
840
+
841
+ buffer = create_unicode_buffer(buffer_size + 1)
842
+
843
+ local_string.GetString(idx, buffer, len(buffer))
844
+
845
+ strings.append(buffer.value)
846
+
847
+ local_string.Release()
848
+
849
+ return strings
850
+
672
851
 
673
852
  class Win32DirectWriteFont(base.Font):
674
853
  """DirectWrite Font object for Windows 7+."""
854
+
675
855
  # To load fonts from files, we need to produce a custom collection.
676
856
  _custom_collection = None
677
857
 
@@ -688,31 +868,27 @@ class Win32DirectWriteFont(base.Font):
688
868
  _font_cache = []
689
869
  _font_loader_key = None
690
870
 
691
- _default_name = "Segoe UI" # Default font for Windows 7+
692
-
693
871
  _glyph_renderer = None
694
872
  _empty_glyph = None
695
873
  _zero_glyph = None
696
874
 
697
875
  glyph_renderer_class = DirectWriteGlyphRenderer
698
- texture_internalformat = pyglet.gl.GL_RGBA
699
876
 
700
- def __init__(self, name: str, size: float, weight: str = "normal", italic: bool | str = False, # noqa: D107
701
- stretch: bool | str = False, dpi: int | None = None, locale: str | None = None) -> None:
877
+ def __init__( # noqa: D107
878
+ self,
879
+ name: str,
880
+ size: float,
881
+ weight: str = "normal",
882
+ style: str = "normal",
883
+ stretch: str = "normal",
884
+ dpi: int | None = None,
885
+ locale: str | None = None,
886
+ ) -> None:
702
887
  self._filename: str | None = None
703
888
 
704
- super().__init__()
705
-
706
- if not name:
707
- name = self._default_name
889
+ super().__init__(name, size, weight, style, stretch, dpi)
708
890
 
709
891
  self.buffers = []
710
- self._name = name
711
- self.weight = weight
712
- self.size = size
713
- self.italic = italic
714
- self.stretch = stretch
715
- self.dpi = dpi
716
892
  self.locale = locale
717
893
 
718
894
  if self.locale is None:
@@ -720,17 +896,11 @@ class Win32DirectWriteFont(base.Font):
720
896
  self.rtl = False # Right to left should be handled by pyglet?
721
897
  # Use system locale string?
722
898
 
723
- if self.dpi is None:
724
- self.dpi = 96
725
-
726
- # From DPI to DIP (Device Independent Pixels) which is what the fonts rely on.
727
- self.pixel_size = (self.size * self.dpi) // 72
728
-
729
899
  self._weight = name_to_weight[self.weight]
730
900
 
731
- if self.italic:
732
- if isinstance(self.italic, str):
733
- self._style = name_to_style[self.italic]
901
+ if self.style:
902
+ if isinstance(self.style, str):
903
+ self._style = name_to_style[self.style]
734
904
  else:
735
905
  self._style = DWRITE_FONT_STYLE_ITALIC
736
906
  else:
@@ -747,8 +917,11 @@ class Win32DirectWriteFont(base.Font):
747
917
  self._font_index, self._collection = self.get_collection(name)
748
918
  write_font = None
749
919
  # If not font found, search all collections for legacy GDI naming.
750
- if pyglet.options["dw_legacy_naming"] and (self._font_index is None and self._collection is None):
751
- write_font, self._collection = self.find_font_face(name, self._weight, self._style, self._stretch)
920
+ #if pyglet.options["dw_legacy_naming"] and (self._font_index is None and self._collection is None):
921
+ # write_font, self._collection = self.find_font_face(name, self._weight, self._style, self._stretch)
922
+
923
+ # if self._collection is None:
924
+ # write_font, self._collection = self.find_font_face(self._default_name, self._weight, self._style, self._stretch)
752
925
 
753
926
  assert self._collection is not None, f"Font: '{name}' not found in loaded or system font collection."
754
927
 
@@ -793,7 +966,7 @@ class Win32DirectWriteFont(base.Font):
793
966
  self._font_metrics = DWRITE_FONT_METRICS()
794
967
  self.font_face.GetMetrics(byref(self._font_metrics))
795
968
 
796
- self.font_scale_ratio = (self.pixel_size / self._font_metrics.designUnitsPerEm)
969
+ self.font_scale_ratio = self.pixel_size / self._font_metrics.designUnitsPerEm
797
970
 
798
971
  self.ascent = math.ceil(self._font_metrics.ascent * self.font_scale_ratio)
799
972
  self.descent = -round(self._font_metrics.descent * self.font_scale_ratio)
@@ -876,7 +1049,7 @@ class Win32DirectWriteFont(base.Font):
876
1049
  def name(self) -> str:
877
1050
  return self._name
878
1051
 
879
- def render_to_image(self, text: str, width: int=10000, height: int=80) -> ImageData:
1052
+ def render_to_image(self, text: str, width: int = 10000, height: int = 80) -> ImageData:
880
1053
  """This process uses only DirectWrite to shape and render text for layout and graphics.
881
1054
 
882
1055
  This may allow more accurate fonts (bidi, rtl, etc) in very special circumstances at the cost of
@@ -926,51 +1099,36 @@ class Win32DirectWriteFont(base.Font):
926
1099
  metrics = get_glyph_metrics(self.font_face, (UINT16 * len(missing))(*missing), len(missing))
927
1100
 
928
1101
  for idx, glyph_indice in enumerate(missing):
929
- glyph = self._glyph_renderer.render_single_glyph(self.font_face, glyph_indice, metrics[idx],
930
- self._glyph_renderer.measuring_mode)
1102
+ glyph = self._glyph_renderer.render_single_glyph(
1103
+ self.font_face, glyph_indice, metrics[idx], self._glyph_renderer.measuring_mode,
1104
+ )
931
1105
  self.glyphs[glyph_indice] = glyph
932
1106
 
933
- def _get_fallback_glyph(self, text: str) -> base.Glyph:
934
- for fallback in self.fallbacks:
935
- indices, missing = fallback.get_glyph_indices(text)
936
- # If the amount of indices match what's missing, nothing was retrieved.
937
- if len(indices) == len(missing):
938
- continue
939
- # Fallback should render the glyphs it found.
940
- fallback.render_glyph_indices(indices)
941
- for indice in indices:
942
- if indice != 0:
943
- return fallback.glyphs[indice]
944
-
945
- return self.glyphs[0]
946
1107
 
947
- def get_glyphs(self, text: str) -> tuple[list[Glyph], list[base.GlyphPosition]]:
1108
+ def get_glyphs(self, text: str, shaping: bool) -> tuple[list[Glyph], list[base.GlyphPosition]]:
948
1109
  self._initialize_renderer()
949
1110
 
950
- if pyglet.options.text_shaping == 'harfbuzz' and harfbuzz_available():
951
- return get_harfbuzz_shaped_glyphs(self, text)
1111
+ if shaping:
1112
+ if pyglet.options.text_shaping == 'harfbuzz' and harfbuzz_available():
1113
+ return get_harfbuzz_shaped_glyphs(self, text)
952
1114
 
953
- if pyglet.options.text_shaping == 'platform':
954
- self._glyph_renderer.current_glyphs.clear()
955
- self._glyph_renderer.current_offsets.clear()
956
- text_layout = self.create_text_layout(text)
1115
+ if pyglet.options.text_shaping == 'platform':
1116
+ self._glyph_renderer.current_glyphs.clear()
1117
+ self._glyph_renderer.current_offsets.clear()
1118
+ text_layout = self.create_text_layout(text)
957
1119
 
958
- ptr = cast(id(self._glyph_renderer), c_void_p)
959
- text_layout.Draw(ptr, _renderer.as_interface(IDWriteTextRenderer), 0, 0)
960
- text_layout.Release()
1120
+ ptr = cast(id(self._glyph_renderer), c_void_p)
1121
+ text_layout.Draw(ptr, _renderer.as_interface(IDWriteTextRenderer), 0, 0)
1122
+ text_layout.Release()
961
1123
 
962
- return self._glyph_renderer.current_glyphs, self._glyph_renderer.current_offsets
1124
+ return self._glyph_renderer.current_glyphs, self._glyph_renderer.current_offsets
963
1125
 
964
1126
  glyphs = []
965
1127
  offsets = []
966
1128
  indices, missing = self.get_glyph_indices(text)
967
1129
  self.render_glyph_indices(indices)
968
1130
  for i, indice in enumerate(indices):
969
- if indice == 0:
970
- glyph = self._get_fallback_glyph(missing[i])
971
- glyphs.append(glyph)
972
- else:
973
- glyphs.append(self.glyphs[indice])
1131
+ glyphs.append(self.glyphs[indice])
974
1132
  offsets.append(GlyphPosition(0, 0, 0, 0))
975
1133
 
976
1134
  return glyphs, offsets
@@ -984,8 +1142,11 @@ class Win32DirectWriteFont(base.Font):
984
1142
 
985
1143
  text_layout = IDWriteTextLayout()
986
1144
  self._write_factory.CreateTextLayout(
987
- text_buffer, len(text_buffer)-1, self._text_format,
988
- 10000, 10000, # Doesn't affect glyph, bitmap size.
1145
+ text_buffer,
1146
+ len(text_buffer) - 1,
1147
+ self._text_format,
1148
+ 10000,
1149
+ 10000, # Doesn't affect glyph, bitmap size.
989
1150
  byref(text_layout),
990
1151
  )
991
1152
 
@@ -1034,7 +1195,21 @@ class Win32DirectWriteFont(base.Font):
1034
1195
  cls._font_loader_key = cast(create_unicode_buffer("legacy_font_loader"), c_void_p)
1035
1196
 
1036
1197
  @classmethod
1037
- def add_font_data(cls: type[Win32DirectWriteFont], data: BinaryIO) -> None:
1198
+ def get_added_fonts(cls: type[Win32DirectWriteFont]) -> list[str]:
1199
+ """Retrieves the added Font Family names from the custom font set.
1200
+
1201
+ Note that this API functionality is only supported in Windows 10 1607 and higher.
1202
+ """
1203
+ if not cls._write_factory:
1204
+ cls._initialize_direct_write()
1205
+
1206
+ if not cls._font_loader:
1207
+ cls._initialize_custom_loaders()
1208
+
1209
+ return _get_font_collection_family_data(cls._custom_collection)
1210
+
1211
+ @classmethod
1212
+ def add_font_data(cls: type[Win32DirectWriteFont], data: BinaryIO, font_manager: FontManager) -> None:
1038
1213
  if not cls._write_factory:
1039
1214
  cls._initialize_direct_write()
1040
1215
 
@@ -1043,11 +1218,9 @@ class Win32DirectWriteFont(base.Font):
1043
1218
 
1044
1219
  if WINDOWS_10_CREATORS_UPDATE_OR_GREATER:
1045
1220
  font_file = IDWriteFontFile()
1046
- cls._font_loader.CreateInMemoryFontFileReference(cls._write_factory,
1047
- data,
1048
- len(data),
1049
- None,
1050
- byref(font_file))
1221
+ cls._font_loader.CreateInMemoryFontFileReference(
1222
+ cls._write_factory, data, len(data), None, byref(font_file),
1223
+ )
1051
1224
 
1052
1225
  hr = cls._font_builder.AddFontFile(font_file)
1053
1226
  if hr != 0:
@@ -1086,13 +1259,17 @@ class Win32DirectWriteFont(base.Font):
1086
1259
 
1087
1260
  cls._custom_collection = IDWriteFontCollection()
1088
1261
 
1089
- cls._write_factory.CreateCustomFontCollection(cls._font_collection_loader,
1090
- cls._font_loader_key,
1091
- sizeof(cls._font_loader_key),
1092
- byref(cls._custom_collection))
1262
+ cls._write_factory.CreateCustomFontCollection(
1263
+ cls._font_collection_loader,
1264
+ cls._font_loader_key,
1265
+ sizeof(cls._font_loader_key),
1266
+ byref(cls._custom_collection),
1267
+ )
1268
+
1269
+ font_manager._add_loaded_font(cls.get_added_fonts())
1093
1270
 
1094
1271
  @classmethod
1095
- def get_collection(cls: type[Win32DirectWriteFont], font_name: str) -> tuple[int | None, IDWriteFontCollection1 | None]:
1272
+ def get_collection(cls: type[Win32DirectWriteFont], name: str) -> tuple[int | None, IDWriteFontCollection1 | None]:
1096
1273
  """Obtain a collection of fonts based on the font name.
1097
1274
 
1098
1275
  Returns:
@@ -1107,9 +1284,9 @@ class Win32DirectWriteFont(base.Font):
1107
1284
 
1108
1285
  # Check custom loaded font collections.
1109
1286
  if cls._custom_collection:
1110
- cls._custom_collection.FindFamilyName(create_unicode_buffer(font_name),
1111
- byref(font_index),
1112
- byref(font_exists))
1287
+ cls._custom_collection.FindFamilyName(
1288
+ create_unicode_buffer(name), byref(font_index), byref(font_exists),
1289
+ )
1113
1290
 
1114
1291
  if font_exists.value:
1115
1292
  return font_index.value, cls._custom_collection
@@ -1119,9 +1296,7 @@ class Win32DirectWriteFont(base.Font):
1119
1296
  sys_collection = IDWriteFontCollection()
1120
1297
  if not font_exists.value:
1121
1298
  cls._write_factory.GetSystemFontCollection(byref(sys_collection), 1)
1122
- sys_collection.FindFamilyName(create_unicode_buffer(font_name),
1123
- byref(font_index),
1124
- byref(font_exists))
1299
+ sys_collection.FindFamilyName(create_unicode_buffer(name), byref(font_index), byref(font_exists))
1125
1300
 
1126
1301
  if font_exists.value:
1127
1302
  return font_index.value, sys_collection
@@ -1129,8 +1304,9 @@ class Win32DirectWriteFont(base.Font):
1129
1304
  return None, None
1130
1305
 
1131
1306
  @classmethod
1132
- def find_font_face(cls, font_name: str, weight: str, italic: bool | str, stretch: bool | str) -> tuple[
1133
- IDWriteFont | None, IDWriteFontCollection | None]:
1307
+ def find_font_face(
1308
+ cls, font_name: str, weight: str, italic: str, stretch: str,
1309
+ ) -> tuple[IDWriteFont | None, IDWriteFontCollection | None]:
1134
1310
  """Search font collections for legacy RBIZ names.
1135
1311
 
1136
1312
  Matching to weight, italic, stretch is problematic in that there are many values. Attempt to parse the font
@@ -1199,7 +1375,14 @@ class Win32DirectWriteFont(base.Font):
1199
1375
  return found_weight, found_style, found_stretch
1200
1376
 
1201
1377
  @staticmethod
1202
- def find_legacy_font(collection: IDWriteFontCollection, font_name: str, weight: str, italic: bool | str, stretch: bool | str, full_debug: bool=False) -> IDWriteFont | None:
1378
+ def find_legacy_font(
1379
+ collection: IDWriteFontCollection,
1380
+ font_name: str,
1381
+ weight: str,
1382
+ style: bool | str,
1383
+ stretch: bool | str,
1384
+ full_debug: bool = False,
1385
+ ) -> IDWriteFont | None:
1203
1386
  coll_count = collection.GetFontFamilyCount()
1204
1387
 
1205
1388
  assert _debug_print(f"directwrite: Found {coll_count} fonts in collection.")
@@ -1214,7 +1397,7 @@ class Win32DirectWriteFont(base.Font):
1214
1397
  family_name_str = IDWriteLocalizedStrings()
1215
1398
  family.GetFamilyNames(byref(family_name_str))
1216
1399
 
1217
- family_names = Win32DirectWriteFont.unpack_localized_string(family_name_str, locale)
1400
+ family_names = _unpack_localized_string(family_name_str, locale)
1218
1401
  family_name = family_names[0]
1219
1402
 
1220
1403
  if family_name[0] != font_name[0]:
@@ -1236,7 +1419,7 @@ class Win32DirectWriteFont(base.Font):
1236
1419
  fc_str = IDWriteLocalizedStrings()
1237
1420
  temp_ft.GetFaceNames(byref(fc_str))
1238
1421
 
1239
- strings = Win32DirectWriteFont.unpack_localized_string(fc_str, locale)
1422
+ strings = _unpack_localized_string(fc_str, locale)
1240
1423
  face_names.extend(strings)
1241
1424
 
1242
1425
  assert _debug_print(f"directwrite: Face names found: {strings}")
@@ -1244,18 +1427,19 @@ class Win32DirectWriteFont(base.Font):
1244
1427
  # Check for GDI compatibility name
1245
1428
  compat_names = IDWriteLocalizedStrings()
1246
1429
  exists = BOOL()
1247
- temp_ft.GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES,
1248
- byref(compat_names),
1249
- byref(exists))
1430
+ temp_ft.GetInformationalStrings(
1431
+ DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES, byref(compat_names), byref(exists),
1432
+ )
1250
1433
 
1251
1434
  # Successful in finding GDI name.
1252
1435
  match_found = False
1253
1436
  if exists.value != 0:
1254
- for compat_name in Win32DirectWriteFont.unpack_localized_string(compat_names, locale):
1437
+ for compat_name in _unpack_localized_string(compat_names, locale):
1255
1438
  if compat_name == font_name:
1256
1439
  assert _debug_print(
1257
1440
  f"Found legacy name '{font_name}' as '{family_name}' in font face '{j}' (collection "
1258
- f"id #{i}).")
1441
+ f"id #{i}).",
1442
+ )
1259
1443
 
1260
1444
  match_found = True
1261
1445
  matches.append((temp_ft.GetWeight(), temp_ft.GetStyle(), temp_ft.GetStretch(), temp_ft))
@@ -1269,7 +1453,7 @@ class Win32DirectWriteFont(base.Font):
1269
1453
 
1270
1454
  # If we have matches, we've already parsed through the proper family. Now try to match.
1271
1455
  if matches:
1272
- write_font = Win32DirectWriteFont.match_closest_font(matches, weight, italic, stretch)
1456
+ write_font = Win32DirectWriteFont.match_closest_font(matches, weight, style, stretch)
1273
1457
 
1274
1458
  # Cleanup other matches not used.
1275
1459
  for match in matches:
@@ -1281,7 +1465,9 @@ class Win32DirectWriteFont(base.Font):
1281
1465
  return None
1282
1466
 
1283
1467
  @staticmethod
1284
- def match_closest_font(font_list: list[tuple[int, int, int, IDWriteFont]], weight: str, italic: int, stretch: int) -> IDWriteFont | None:
1468
+ def match_closest_font(
1469
+ font_list: list[tuple[int, int, int, IDWriteFont]], weight: str, italic: int, stretch: int,
1470
+ ) -> IDWriteFont | None:
1285
1471
  """Match the closest font to the parameters specified.
1286
1472
 
1287
1473
  If a full match is not found, a secondary match will be found based on similar features. This can probably
@@ -1294,7 +1480,8 @@ class Win32DirectWriteFont(base.Font):
1294
1480
  # Found perfect match, no need for the rest.
1295
1481
  if f_weight == weight and f_style == italic and f_stretch == stretch:
1296
1482
  _debug_print(
1297
- f"directwrite: full match found. (weight: {f_weight}, italic: {f_style}, stretch: {f_stretch})")
1483
+ f"directwrite: full match found. (weight: {f_weight}, italic: {f_style}, stretch: {f_stretch})",
1484
+ )
1298
1485
  return writefont
1299
1486
 
1300
1487
  prop_match = 0
@@ -1324,56 +1511,14 @@ class Win32DirectWriteFont(base.Font):
1324
1511
  if closest:
1325
1512
  # Take the first match after sorting.
1326
1513
  closest_match = closest[0]
1327
- _debug_print(f"directwrite: falling back to partial match. "
1328
- f"(weight: {closest_match[2]}, italic: {closest_match[3]}, stretch: {closest_match[4]})")
1514
+ _debug_print(
1515
+ f"directwrite: falling back to partial match. "
1516
+ f"(weight: {closest_match[2]}, italic: {closest_match[3]}, stretch: {closest_match[4]})",
1517
+ )
1329
1518
  return closest_match[5]
1330
1519
 
1331
1520
  return None
1332
1521
 
1333
- @staticmethod
1334
- def unpack_localized_string(local_string: IDWriteLocalizedStrings, locale: str) -> list[str]:
1335
- """Takes IDWriteLocalizedStrings and unpacks the strings inside of it into a list."""
1336
- str_array_len = local_string.GetCount()
1337
-
1338
- strings = []
1339
- for _ in range(str_array_len):
1340
- string_size = UINT32()
1341
-
1342
- idx = Win32DirectWriteFont.get_localized_index(local_string, locale)
1343
-
1344
- local_string.GetStringLength(idx, byref(string_size))
1345
-
1346
- buffer_size = string_size.value
1347
-
1348
- buffer = create_unicode_buffer(buffer_size + 1)
1349
-
1350
- local_string.GetString(idx, buffer, len(buffer))
1351
-
1352
- strings.append(buffer.value)
1353
-
1354
- local_string.Release()
1355
-
1356
- return strings
1357
-
1358
- @staticmethod
1359
- def get_localized_index(strings: IDWriteLocalizedStrings, locale: str) -> int:
1360
- idx = UINT32()
1361
- exists = BOOL()
1362
-
1363
- if locale:
1364
- strings.FindLocaleName(locale, byref(idx), byref(exists))
1365
-
1366
- if not exists.value:
1367
- # fallback to english.
1368
- strings.FindLocaleName("en-us", byref(idx), byref(exists))
1369
-
1370
- if not exists:
1371
- return 0
1372
-
1373
- return idx.value
1374
-
1375
- return 0
1376
-
1377
1522
 
1378
1523
  d2d_factory = ID2D1Factory()
1379
1524
  hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_ID2D1Factory, None, byref(d2d_factory))