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/quartz.py CHANGED
@@ -2,17 +2,18 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import math
5
- import warnings
6
5
  from ctypes import byref, c_int32, c_void_p, string_at, cast, c_char_p, c_float
7
- from typing import BinaryIO
6
+ from enum import Enum
7
+ from typing import BinaryIO, TYPE_CHECKING
8
8
 
9
9
  import pyglet.image
10
- from pyglet.font import base
11
- from pyglet.font.base import Glyph, GlyphPosition
12
- from pyglet.libs.darwin import CGFloat, cocoapy, kCTFontURLAttribute, cfnumber_to_number, \
13
- kCTFontWeightTrait
14
- from pyglet.font.harfbuzz import harfbuzz_available, get_resource_from_ct_font, \
15
- get_harfbuzz_shaped_glyphs
10
+ from pyglet.enums import Weight, Stretch
11
+ from pyglet.font.base import Glyph, GlyphPosition, GlyphRenderer, Font, get_grapheme_clusters
12
+ from pyglet.libs.darwin import CGFloat, cocoapy, kCTFontURLAttribute, cfnumber_to_number, CGPoint, CFRange
13
+ from pyglet.font.harfbuzz import harfbuzz_available, get_resource_from_ct_font, get_harfbuzz_shaped_glyphs
14
+
15
+ if TYPE_CHECKING:
16
+ from pyglet.font import FontManager
16
17
 
17
18
 
18
19
  cf = cocoapy.cf
@@ -29,10 +30,37 @@ UIFontWeightBold = 0.4
29
30
  UIFontWeightHeavy = 0.56
30
31
  UIFontWeightBlack = 0.62
31
32
 
33
+ _UIFontWeightMap: tuple[tuple[float, Weight], ...] = (
34
+ (-0.8, Weight.EXTRALIGHT), # ultra light
35
+ (-0.6, Weight.THIN),
36
+ (-0.4, Weight.LIGHT),
37
+ (0.0, Weight.NORMAL),
38
+ (0.23, Weight.MEDIUM),
39
+ (0.3, Weight.SEMIBOLD),
40
+ (0.4, Weight.BOLD),
41
+ (0.56, Weight.EXTRABOLD),
42
+ (0.62, Weight.BLACK),
43
+ )
44
+
45
+ def _quartz_font_mapping_to_enum(mapping_tuple: tuple, value: float) -> Enum:
46
+ """Gets the closest matching value for value."""
47
+ closest = min(mapping_tuple, key=lambda w: abs(w[0] - value))
48
+ return closest[1]
49
+
50
+ def _quartz_font_weight_to_enum(weight: float) -> Weight:
51
+ """Map a UIFont weight float to the closest Weight enum.
52
+
53
+ The value returned is a float and may have imprecision.
54
+
55
+ The test font Action Man Bold produces: 0.4000000059604645
56
+ """
57
+ return _quartz_font_mapping_to_enum(_UIFontWeightMap, weight)
58
+
59
+
32
60
  name_to_weight = {
33
- True: UIFontWeightBold, # Bold as default for True
34
- False: UIFontWeightRegular, # Regular for False
35
- None: UIFontWeightRegular, # Regular if no weight provided
61
+ True: UIFontWeightBold, # Bold as default for True
62
+ False: UIFontWeightRegular, # Regular for False
63
+ None: UIFontWeightRegular, # Regular if no weight provided
36
64
  "thin": UIFontWeightThin,
37
65
  "extralight": UIFontWeightUltraLight,
38
66
  "ultralight": UIFontWeightUltraLight,
@@ -67,6 +95,56 @@ name_to_stretch = {
67
95
  "ultraexpanded": 0.4,
68
96
  }
69
97
 
98
+ _UIFontStretchMap = (
99
+ (-0.4, Stretch.ULTRACONDENSED),
100
+ (-0.3, Stretch.EXTRACONDENSED),
101
+ (-0.2, Stretch.CONDENSED),
102
+ (-0.1, Stretch.SEMICONDENSED),
103
+ (0.0, Stretch.NORMAL),
104
+ (-0.1, Stretch.SEMIEXPANDED),
105
+ (0.2, Stretch.EXPANDED),
106
+ (0.3, Stretch.EXTRAEXPANDED),
107
+ (0.4, Stretch.ULTRAEXPANDED),
108
+
109
+ )
110
+
111
+ def _quartz_font_stretch_to_enum(stretch: float) -> Stretch:
112
+ """Map a UIFont weight float to the closest Weight enum.
113
+
114
+ The value returned is a float and may have imprecision.
115
+ """
116
+ return _quartz_font_mapping_to_enum(_UIFontStretchMap, stretch)
117
+
118
+
119
+
120
+ kCTFontUIFontUser = 0
121
+ kCTFontUIFontUserFixedPitch = 1
122
+ kCTFontUIFontSystem = 2
123
+ kCTFontUIFontEmphasizedSystem = 3
124
+ kCTFontUIFontSmallSystem = 4
125
+ kCTFontUIFontSmallEmphasizedSystem = 5
126
+ kCTFontUIFontMiniSystem = 6
127
+ kCTFontUIFontMiniEmphasizedSystem = 7
128
+ kCTFontUIFontViews = 8
129
+ kCTFontUIFontApplication = 9
130
+ kCTFontUIFontLabel = 10
131
+ kCTFontUIFontMenuTitle = 11
132
+ kCTFontUIFontMenuItem = 12
133
+ kCTFontUIFontMenuItemMark = 13
134
+ kCTFontUIFontMenuItemCmdKey = 14
135
+ kCTFontUIFontWindowTitle = 15
136
+ kCTFontUIFontPushButton = 16
137
+ kCTFontUIFontUtilityWindowTitle = 17
138
+ kCTFontUIFontAlertHeader = 18
139
+ kCTFontUIFontSystemDetail = 19
140
+ kCTFontUIFontEmphasizedSystemDetail = 20
141
+ kCTFontUIFontToolbar = 21
142
+ kCTFontUIFontSmallToolbar = 22
143
+ kCTFontUIFontMessage = 23
144
+ kCTFontUIFontPalette = 24
145
+ kCTFontUIFontToolTip = 25
146
+ kCTFontUIFontControlContent = 26
147
+
70
148
 
71
149
  if harfbuzz_available():
72
150
  """Build the callbacks and information needed for Harfbuzz to work with CoreText Fonts.
@@ -75,7 +153,12 @@ if harfbuzz_available():
75
153
  retrieve the full font bytes from memory, we must construct callbacks for harfbuzz
76
154
  to retrieve the tag tables.
77
155
  """
78
- from pyglet.font.harfbuzz.harfbuzz_lib import hb_lib, hb_destroy_func_t, hb_reference_table_func_t, HB_MEMORY_MODE_READONLY
156
+ from pyglet.font.harfbuzz.harfbuzz_lib import (
157
+ hb_lib,
158
+ hb_destroy_func_t,
159
+ hb_reference_table_func_t,
160
+ HB_MEMORY_MODE_READONLY,
161
+ )
79
162
 
80
163
  def py_coretext_table_data_destroy(user_data: c_void_p):
81
164
  """Release the table resources once harfbuzz is done."""
@@ -105,33 +188,33 @@ if harfbuzz_available():
105
188
 
106
189
  # Create a blob that references this table data.
107
190
  data_ptr_char = cast(data_ptr, c_char_p)
108
- blob = hb_lib.hb_blob_create(data_ptr_char, length, HB_MEMORY_MODE_READONLY,
109
- table_data, py_coretext_table_data_destroy_c)
191
+ blob = hb_lib.hb_blob_create(
192
+ data_ptr_char, length, HB_MEMORY_MODE_READONLY, table_data, py_coretext_table_data_destroy_c,
193
+ )
110
194
  return blob
111
195
 
112
196
  # Null callback, so harfbuzz cannot destroy our CGFont.
113
197
  _destroy_callback_null = cast(None, hb_destroy_func_t)
114
198
 
115
- class QuartzGlyphRenderer(base.GlyphRenderer):
199
+
200
+ class QuartzGlyphRenderer(GlyphRenderer):
116
201
  font: QuartzFont
117
202
 
118
203
  def __init__(self, font: QuartzFont) -> None:
119
204
  super().__init__(font)
120
205
  self.font = font
121
206
 
122
- def render_index(self, glyph_index: int):
123
- ctFont = self.font.ctFont
124
-
207
+ def render_index(self, ct_font: c_void_p, glyph_index: int) -> None:
125
208
  # Create an attributed string using text and font.
126
209
  # Determine the glyphs involved for the text (if any)
127
210
  # Get a bounding rectangle for glyphs in string.
128
211
  count = 1
129
212
  glyphs = (cocoapy.CGGlyph * count)(glyph_index)
130
213
 
131
- rect = ct.CTFontGetBoundingRectsForGlyphs(ctFont, 0, glyphs, None, count)
214
+ rect = ct.CTFontGetBoundingRectsForGlyphs(ct_font, 0, glyphs, None, count)
132
215
 
133
216
  # Get advance for all glyphs in string.
134
- advance = ct.CTFontGetAdvancesForGlyphs(ctFont, 0, glyphs, None, count)
217
+ advance = ct.CTFontGetAdvancesForGlyphs(ct_font, 0, glyphs, None, count)
135
218
 
136
219
  # Set image parameters:
137
220
  # We add 2 pixels to the bitmap width and height so that there will be a 1-pixel border
@@ -148,26 +231,29 @@ class QuartzGlyphRenderer(base.GlyphRenderer):
148
231
  bits_per_components = 8
149
232
  bytes_per_row = 4 * width
150
233
  colorSpace = c_void_p(quartz.CGColorSpaceCreateDeviceRGB())
151
- bitmap_context = c_void_p(quartz.CGBitmapContextCreate(
152
- None,
153
- width,
154
- height,
155
- bits_per_components,
156
- bytes_per_row,
157
- colorSpace,
158
- cocoapy.kCGImageAlphaPremultipliedLast))
234
+ bitmap_context = c_void_p(
235
+ quartz.CGBitmapContextCreate(
236
+ None,
237
+ width,
238
+ height,
239
+ bits_per_components,
240
+ bytes_per_row,
241
+ colorSpace,
242
+ cocoapy.kCGImageAlphaPremultipliedLast,
243
+ ),
244
+ )
159
245
 
160
246
  # Draw text to bitmap context.
161
247
  quartz.CGContextSetShouldAntialias(bitmap_context, pyglet.options.text_antialiasing)
162
248
  quartz.CGContextSetTextPosition(bitmap_context, -lsb, baseline)
163
249
  quartz.CGContextSetRGBFillColor(bitmap_context, 1, 1, 1, 1)
164
- quartz.CGContextSetFont(bitmap_context, ctFont)
250
+ quartz.CGContextSetFont(bitmap_context, ct_font)
165
251
  quartz.CGContextSetFontSize(bitmap_context, self.font.pixel_size)
166
252
  quartz.CGContextTranslateCTM(bitmap_context, 0, height) # Move origin to top-left
167
253
  quartz.CGContextScaleCTM(bitmap_context, 1, -1) # Flip vertically
168
254
 
169
255
  positions = (cocoapy.CGPoint * 1)(*[cocoapy.CGPoint(0, 0)])
170
- quartz.CTFontDrawGlyphs(ctFont, glyphs, positions, 1, bitmap_context)
256
+ quartz.CTFontDrawGlyphs(ct_font, glyphs, positions, 1, bitmap_context)
171
257
 
172
258
  # Create an image to get the data out.
173
259
  image_ref = c_void_p(quartz.CGBitmapContextCreateImage(bitmap_context))
@@ -194,7 +280,7 @@ class QuartzGlyphRenderer(base.GlyphRenderer):
194
280
 
195
281
  raise Exception("CG Image buffer could not be read.")
196
282
 
197
- def render(self, text: str) -> base.Glyph:
283
+ def render(self, text: str) -> Glyph:
198
284
  # Using CTLineDraw seems to be the only way to make sure that the text
199
285
  # is drawn with the specified font when that font is a graphics font loaded from
200
286
  # memory. For whatever reason, [NSAttributedString drawAtPoint:] ignores
@@ -205,10 +291,10 @@ class QuartzGlyphRenderer(base.GlyphRenderer):
205
291
 
206
292
  # Create an attributed string using text and font.
207
293
  attributes = c_void_p(
208
- cf.CFDictionaryCreateMutable(None, 2, cf.kCFTypeDictionaryKeyCallBacks, cf.kCFTypeDictionaryValueCallBacks)
294
+ cf.CFDictionaryCreateMutable(None, 2, cf.kCFTypeDictionaryKeyCallBacks, cf.kCFTypeDictionaryValueCallBacks),
209
295
  )
210
296
  cf.CFDictionaryAddValue(attributes, cocoapy.kCTFontAttributeName, ctFont)
211
- cf.CFDictionaryAddValue(attributes, cocoapy.kCTForegroundColorFromContextAttributeName , cocoapy.kCFBooleanTrue)
297
+ cf.CFDictionaryAddValue(attributes, cocoapy.kCTForegroundColorFromContextAttributeName, cocoapy.kCFBooleanTrue)
212
298
  cf_str = cocoapy.CFSTR(text)
213
299
  string = c_void_p(cf.CFAttributedStringCreate(None, cf_str, attributes))
214
300
 
@@ -256,14 +342,21 @@ class QuartzGlyphRenderer(base.GlyphRenderer):
256
342
  bits_per_components = 8
257
343
  bytes_per_row = 4 * width
258
344
  colorSpace = c_void_p(quartz.CGColorSpaceCreateDeviceRGB())
259
- bitmap_context = c_void_p(quartz.CGBitmapContextCreate(
260
- None,
261
- width,
262
- height,
263
- bits_per_components,
264
- bytes_per_row,
265
- colorSpace,
266
- cocoapy.kCGImageAlphaPremultipliedLast))
345
+ bitmap_context = c_void_p(
346
+ quartz.CGBitmapContextCreate(
347
+ None,
348
+ width,
349
+ height,
350
+ bits_per_components,
351
+ bytes_per_row,
352
+ colorSpace,
353
+ cocoapy.kCGImageAlphaPremultipliedLast,
354
+ ),
355
+ )
356
+
357
+ # Transform the context to a top-left origin.
358
+ # quartz.CGContextTranslateCTM(bitmap, 0, height) # Move origin to top-left.
359
+ # quartz.CGContextScaleCTM(bitmap, 1.0, -1.0) # Flip Y-axis.
267
360
 
268
361
  # Draw text to bitmap context.
269
362
  quartz.CGContextSetShouldAntialias(bitmap_context, pyglet.options.text_antialiasing)
@@ -293,78 +386,96 @@ class QuartzGlyphRenderer(base.GlyphRenderer):
293
386
  glyph_image = pyglet.image.ImageData(width, height, "RGBA", buffer, bytes_per_row)
294
387
 
295
388
  glyph = self.font.create_glyph(glyph_image)
296
- glyph.set_bearings(baseline, lsb, advance)
389
+
390
+ # Flip baseline if it's vulkan since it's top left based.
391
+ # TODO: Revisit when flipping OpenGL coordinates.
392
+ if pyglet.options.backend == "vulkan":
393
+ glyph.set_bearings(height - baseline - self.font.descent - self.font.ascent, lsb, advance)
394
+ else:
395
+ glyph.set_bearings(baseline, lsb, advance)
297
396
 
298
397
  return glyph
299
398
 
300
399
  raise Exception("CG Image buffer could not be read.")
301
400
 
302
401
 
303
- class QuartzFont(base.Font):
304
- glyph_renderer_class: type[base.GlyphRenderer] = QuartzGlyphRenderer
402
+ class QuartzFont(Font):
403
+ glyph_renderer_class: type[GlyphRenderer] = QuartzGlyphRenderer
305
404
  _loaded_CGFont_table: dict[str, dict[int, tuple[c_void_p, bytes]]] = {}
306
405
 
307
- def __init__(self, name: str, size: float, weight: str = "normal", italic: bool = False, stretch: bool = False,
308
- dpi: int | None = None) -> None:
309
-
310
- super().__init__()
311
-
312
- name = name or "Helvetica"
313
-
314
- self.dpi = dpi or 96
315
- self.size = size
316
- self.pixel_size = size * dpi / 72.0
317
- self.italic = italic
318
- self.stretch = stretch
319
- self.weight = weight
320
-
321
- if isinstance(weight, str):
322
- self.weight_value = name_to_weight[weight]
323
- elif weight is True:
324
- self.weight_value = name_to_weight["bold"]
325
- else:
326
- self.weight_value = None
327
-
328
- self.italic = italic
329
-
330
- # Construct traits value.
331
- traits = 0
332
- if italic:
333
- traits |= cocoapy.kCTFontItalicTrait
334
-
335
- if isinstance(stretch, str):
336
- self.stretch_value = name_to_stretch[stretch]
337
- else:
338
- self.stretch_value = None
339
-
340
- name = str(name)
341
- self.traits = traits
406
+ def __init__(
407
+ self,
408
+ name: str,
409
+ size: float,
410
+ weight: str = "normal",
411
+ style: str = "normal",
412
+ stretch: str = "normal",
413
+ dpi: int | None = None,
414
+ ) -> None:
415
+ super().__init__(name, size, weight, style, stretch, dpi)
416
+
417
+ # Weight value
418
+ self._weight = name_to_weight[weight]
419
+
420
+ # Construct traits value for italics/obliques.
421
+ self._italic = 0
422
+ if style != "normal" and style is not False:
423
+ self._italic |= cocoapy.kCTFontItalicTrait
424
+
425
+ self._stretch = name_to_stretch[stretch]
342
426
  # First see if we can find an appropriate font from our table of loaded fonts.
343
- result = self._lookup_font_with_family_and_traits(name, traits)
344
- if result:
345
- cgFont = result[0]
346
- # Use cgFont from table to create a CTFont object with the specified size.
347
- self.ctFont = c_void_p(ct.CTFontCreateWithGraphicsFont(cgFont, self.pixel_size, None, None))
427
+
428
+ # MacOS protects default fonts from being queried by name, it has to be done by an enumeration and loaded
429
+ # using a separate function.
430
+ if name == "System Default":
431
+ self.ctFont = self._get_system_default(weight, style)
348
432
  else:
349
- # Create a font descriptor for given name and traits and use it to create font.
350
- descriptor = self._create_font_descriptor(name, traits, self.weight_value, self.stretch_value)
351
- self.ctFont = c_void_p(ct.CTFontCreateWithFontDescriptor(descriptor, self.pixel_size, None))
352
- cf.CFRelease(descriptor)
353
- assert self.ctFont, "Couldn't load font: " + name
433
+ result = self._lookup_font_with_family_and_traits(name, self._italic)
434
+ if result:
435
+ cgFont = result[0]
436
+ # Use cgFont from table to create a CTFont object with the specified size.
437
+ self.ctFont = c_void_p(ct.CTFontCreateWithGraphicsFont(cgFont, self.pixel_size, None, None))
438
+ else:
439
+ # Create a font descriptor for given name and traits and use it to create font.
440
+ descriptor = self._create_font_descriptor(name, self._italic, self._weight, self._stretch)
441
+ self.ctFont = c_void_p(ct.CTFontCreateWithFontDescriptor(descriptor, self.pixel_size, None))
442
+ cf.CFRelease(descriptor)
443
+ assert self.ctFont, "Couldn't load font: " + name
354
444
 
355
445
  string = c_void_p(ct.CTFontCopyFamilyName(self.ctFont))
356
446
  self._family_name = str(cocoapy.cfstring_to_string(string))
357
447
  cf.CFRelease(string)
358
448
 
359
- self.ascent = int(math.ceil(ct.CTFontGetAscent(self.ctFont)))
360
- self.descent = -int(math.ceil(ct.CTFontGetDescent(self.ctFont)))
449
+ self.ascent = math.ceil(ct.CTFontGetAscent(self.ctFont))
450
+ self.descent = -math.ceil(ct.CTFontGetDescent(self.ctFont))
361
451
 
362
452
  if pyglet.options.text_shaping == 'harfbuzz' and harfbuzz_available():
363
453
  self._cg_font = None
364
454
  self.hb_resource = get_resource_from_ct_font(self)
365
455
 
366
-
367
- def _get_hb_face(self):
456
+ def _get_system_default(self, weight: str, italic_style: str) -> c_void_p:
457
+ if weight == "bold":
458
+ font_enum = kCTFontUIFontEmphasizedSystem
459
+ else:
460
+ font_enum = kCTFontUIFontSystem
461
+
462
+ base_ft = ct.CTFontCreateUIFontForLanguage(font_enum, float(self.pixel_size), None)
463
+ final_ft = base_ft
464
+ if italic_style != "normal":
465
+ italic_font = ct.CTFontCreateCopyWithSymbolicTraits(
466
+ base_ft,
467
+ 0.0,
468
+ None,
469
+ cocoapy.kCTFontItalicTrait,
470
+ cocoapy.kCTFontItalicTrait,
471
+ )
472
+ final_ft = italic_font
473
+ cf.CFRelease(base_ft)
474
+
475
+ ct_font = c_void_p(final_ft)
476
+ return ct_font
477
+
478
+ def _get_hb_face(self) -> None:
368
479
  assert self._cg_font is None
369
480
 
370
481
  # Create a CGFont from the CTFont for the face.
@@ -384,7 +495,7 @@ class QuartzFont(base.Font):
384
495
  """
385
496
  filename = self.filename
386
497
  if filename == "Unknown":
387
- result = self._lookup_font_with_family_and_traits(self.name, self.traits)
498
+ result = self._lookup_font_with_family_and_traits(self.name, self._traits)
388
499
  if result:
389
500
  _, data = result
390
501
  else:
@@ -397,7 +508,7 @@ class QuartzFont(base.Font):
397
508
 
398
509
  @property
399
510
  def filename(self) -> str:
400
- descriptor = self._create_font_descriptor(self.name, self.traits, self.weight_value, self.stretch_value)
511
+ descriptor = self._create_font_descriptor(self.name, self._italic, self._weight, self._stretch)
401
512
  ref = c_void_p(ct.CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute))
402
513
  if ref:
403
514
  url = cocoapy.ObjCInstance(ref) # NSURL
@@ -411,7 +522,7 @@ class QuartzFont(base.Font):
411
522
 
412
523
  @property
413
524
  def name(self) -> str:
414
- return self._family_name
525
+ return self._name
415
526
 
416
527
  def __del__(self) -> None:
417
528
  cf.CFRelease(self.ctFont)
@@ -434,7 +545,7 @@ class QuartzFont(base.Font):
434
545
  if traits in fonts:
435
546
  return fonts[traits]
436
547
  # Otherwise try to find a font with some of the traits.
437
- for (t, f) in fonts.items():
548
+ for t, f in fonts.items():
438
549
  if traits & t:
439
550
  return f
440
551
  # Otherwise try to return a regular font.
@@ -443,24 +554,28 @@ class QuartzFont(base.Font):
443
554
  # Otherwise return whatever we have.
444
555
  return list(fonts.values())[0]
445
556
 
446
- def _create_font_descriptor(self, family_name: str, traits: int,
447
- weight: float | None = None,
448
- stretch: float | None = None) -> c_void_p:
557
+ def _create_font_descriptor(
558
+ self, family_name: str, italic_traits: int, weight: float | None = None, stretch: float | None = None,
559
+ ) -> c_void_p:
449
560
  # Create an attribute dictionary.
450
561
  attributes = c_void_p(
451
- cf.CFDictionaryCreateMutable(None, 0, cf.kCFTypeDictionaryKeyCallBacks, cf.kCFTypeDictionaryValueCallBacks))
562
+ cf.CFDictionaryCreateMutable(None, 0, cf.kCFTypeDictionaryKeyCallBacks, cf.kCFTypeDictionaryValueCallBacks),
563
+ )
452
564
  # Add family name to attributes.
453
565
  cfname = cocoapy.CFSTR(family_name)
454
566
  cf.CFDictionaryAddValue(attributes, cocoapy.kCTFontFamilyNameAttribute, cfname)
455
567
  cf.CFRelease(cfname)
456
568
 
457
569
  # Construct a CFNumber to represent the traits.
458
- itraits = c_int32(traits)
570
+ itraits = c_int32(italic_traits)
459
571
  symTraits = c_void_p(cf.CFNumberCreate(None, cocoapy.kCFNumberSInt32Type, byref(itraits)))
460
572
  if symTraits:
461
573
  # Construct a dictionary to hold the traits values.
462
- traitsDict = c_void_p(cf.CFDictionaryCreateMutable(None, 0, cf.kCFTypeDictionaryKeyCallBacks,
463
- cf.kCFTypeDictionaryValueCallBacks))
574
+ traitsDict = c_void_p(
575
+ cf.CFDictionaryCreateMutable(
576
+ None, 0, cf.kCFTypeDictionaryKeyCallBacks, cf.kCFTypeDictionaryValueCallBacks,
577
+ ),
578
+ )
464
579
  if traitsDict:
465
580
  if weight is not None:
466
581
  weight_value = c_float(weight)
@@ -498,7 +613,7 @@ class QuartzFont(base.Font):
498
613
  descriptor = ct.CTFontDescriptorCreateWithNameAndSize(cocoapy.CFSTR(name), 0.0)
499
614
  matched = ct.CTFontDescriptorCreateMatchingFontDescriptor(descriptor, None)
500
615
 
501
- exists = True if matched else False
616
+ exists = bool(matched)
502
617
 
503
618
  if descriptor:
504
619
  cf.CFRelease(descriptor)
@@ -509,7 +624,7 @@ class QuartzFont(base.Font):
509
624
  return exists
510
625
 
511
626
  @classmethod
512
- def add_font_data(cls: type[QuartzFont], data: BinaryIO) -> None:
627
+ def add_font_data(cls: type[QuartzFont], data: BinaryIO, manager: FontManager) -> None:
513
628
  # Create a cgFont with the data. There doesn't seem to be a way to
514
629
  # register a font loaded from memory such that the operating system will
515
630
  # find it later. So instead we just store the cgFont in a table where
@@ -524,7 +639,7 @@ class QuartzFont(base.Font):
524
639
  quartz.CGDataProviderRelease(provider)
525
640
 
526
641
  # Create a template CTFont from the graphics font so that we can get font info.
527
- ctFont = c_void_p(ct.CTFontCreateWithGraphicsFont(cgFont, 1, None, None))
642
+ ctFont = c_void_p(ct.CTFontCreateWithGraphicsFont(cgFont, 1.0, None, None))
528
643
 
529
644
  # Get info about the font to use as key in our font table.
530
645
  string = c_void_p(ct.CTFontCopyFamilyName(ctFont))
@@ -536,6 +651,9 @@ class QuartzFont(base.Font):
536
651
  cf.CFRelease(string)
537
652
 
538
653
  traits = ct.CTFontGetSymbolicTraits(ctFont)
654
+
655
+ weight, italic, stretch = cls._get_font_style_traits(ctFont)
656
+
539
657
  cf.CFRelease(ctFont)
540
658
 
541
659
  # Store font in table. We store it under both its family name and its
@@ -548,11 +666,13 @@ class QuartzFont(base.Font):
548
666
  cls._loaded_CGFont_table[fullName] = {}
549
667
  cls._loaded_CGFont_table[fullName][traits] = (cgFont, data)
550
668
 
669
+ manager._add_loaded_font({(familyName, weight, italic, stretch)}) # noqa: SLF001
670
+
551
671
  def get_text_size(self, text: str) -> tuple[int, int]:
552
672
  ctFont = self.ctFont
553
673
 
554
674
  attributes = c_void_p(
555
- cf.CFDictionaryCreateMutable(None, 1, cf.kCFTypeDictionaryKeyCallBacks, cf.kCFTypeDictionaryValueCallBacks)
675
+ cf.CFDictionaryCreateMutable(None, 1, cf.kCFTypeDictionaryKeyCallBacks, cf.kCFTypeDictionaryValueCallBacks),
556
676
  )
557
677
  cf.CFDictionaryAddValue(attributes, cocoapy.kCTFontAttributeName, ctFont)
558
678
  cf_str = cocoapy.CFSTR(text)
@@ -570,32 +690,99 @@ class QuartzFont(base.Font):
570
690
  cf.CFRelease(cf_str)
571
691
  return round(width), round(height)
572
692
 
573
- def _get_font_weight(self):
574
- traits = ct.CTFontCopyTraits(self.ctFont)
575
- font_weight = cfnumber_to_number(c_void_p(cf.CFDictionaryGetValue(traits, kCTFontWeightTrait)))
576
- cf.CFRelease(traits)
577
- return font_weight
578
-
579
- def _get_font_stretch(self):
580
- traits = ct.CTFontCopyTraits(self.ctFont)
693
+ @staticmethod
694
+ def _get_font_style_traits(ctFont: c_void_p) -> tuple[Weight, bool, Stretch]:
695
+ traits = ct.CTFontCopyTraits(ctFont)
696
+ font_weight = cfnumber_to_number(c_void_p(cf.CFDictionaryGetValue(traits, cocoapy.kCTFontWeightTrait)))
697
+ font_style = cfnumber_to_number(c_void_p(cf.CFDictionaryGetValue(traits, cocoapy.kCTFontSlantTrait)))
581
698
  font_width = cfnumber_to_number(c_void_p(cf.CFDictionaryGetValue(traits, cocoapy.kCTFontWidthTrait)))
582
699
  cf.CFRelease(traits)
583
- return font_width
700
+ return (_quartz_font_weight_to_enum(font_weight),
701
+ True if font_style != 0 else False,
702
+ _quartz_font_stretch_to_enum(font_width))
584
703
 
585
- def render_glyph_indices(self, indices: list[int]) -> None:
704
+ def render_glyph_indices(self, ct_font: c_void_p, font_name: str, indices: list[int]) -> None:
586
705
  # Process any glyphs that have not been rendered.
587
706
  self._initialize_renderer()
588
707
 
589
708
  missing = set()
590
709
  for glyph_indice in set(indices):
591
- if glyph_indice not in self.glyphs:
710
+ dict_key = (font_name, glyph_indice)
711
+ if dict_key not in self.glyphs:
592
712
  missing.add(glyph_indice)
593
713
 
594
714
  # Missing glyphs, get their info.
595
715
  for glyph_indice in missing:
596
- self.glyphs[glyph_indice] = self._glyph_renderer.render_index(glyph_indice)
716
+ self.glyphs[(font_name, glyph_indice)] = self._glyph_renderer.render_index(ct_font, glyph_indice)
717
+
718
+ def _get_shaped_glyphs(self, text: str) -> tuple[list[Glyph], list[GlyphPosition]]:
719
+ attributes = c_void_p(
720
+ cf.CFDictionaryCreateMutable(None, 1, cf.kCFTypeDictionaryKeyCallBacks, cf.kCFTypeDictionaryValueCallBacks)
721
+ )
722
+ cf.CFDictionaryAddValue(attributes, cocoapy.kCTFontAttributeName, self.ctFont)
723
+ cf_str = cocoapy.CFSTR(text)
724
+ string = c_void_p(cf.CFAttributedStringCreate(None, cf_str, attributes))
725
+
726
+ line = c_void_p(ct.CTLineCreateWithAttributedString(string))
727
+ run_array = c_void_p(ct.CTLineGetGlyphRuns(line))
728
+ all_glyphs = []
729
+ all_positions = []
730
+ if run_array:
731
+ for run in cocoapy.cfarray_to_list(run_array):
732
+ count = ct.CTRunGetGlyphCount(run)
733
+
734
+ glyph_array = (cocoapy.CGGlyph * count)()
735
+ position_array = (CGPoint * count)()
597
736
 
598
- def get_glyphs(self, text: str) -> tuple[list[Glyph], list[GlyphPosition]]:
737
+ run_attributes = c_void_p(ct.CTRunGetAttributes(run))
738
+
739
+ # Store Glyph by (font name, index ID), as run's can contain more than 1 font.
740
+ run_ft = c_void_p(cf.CFDictionaryGetValue(run_attributes, cocoapy.kCTFontAttributeName))
741
+ cfstring = c_void_p(ct.CTFontCopyPostScriptName(run_ft))
742
+ font_name = str(cocoapy.cfstring_to_string(cfstring))
743
+
744
+ ct.CTRunGetGlyphs(run, cocoapy.CFRange(0, 0), glyph_array)
745
+ ct.CTRunGetPositions(run, cocoapy.CFRange(0, 0), position_array)
746
+
747
+ # Get the offset of the run inside the full line
748
+ run_start_x = position_array[0].x
749
+ run_start_y = position_array[0].y
750
+
751
+ run_glyphs = []
752
+ run_positions = []
753
+
754
+ # Ensure all glyphs are rendered and stored.
755
+ self.render_glyph_indices(run_ft, font_name, list(glyph_array))
756
+
757
+ for i in range(count):
758
+ glyph_index = glyph_array[i]
759
+
760
+ glyph = self._glyph_renderer.render_index(run_ft, glyph_index)
761
+ run_glyphs.append(glyph)
762
+ current_x = position_array[i].x - run_start_x
763
+ current_y = position_array[i].y - run_start_y
764
+
765
+ if i + 1 < count:
766
+ next_x = position_array[i + 1].x - run_start_x
767
+ next_y = position_array[i + 1].y - run_start_y
768
+ kerning_x = next_x - current_x - run_glyphs[i].advance
769
+ kerning_y = next_y - current_y
770
+ else:
771
+ kerning_x = 0
772
+ kerning_y = 0
773
+
774
+ run_positions.append(GlyphPosition(kerning_x, kerning_y, 0, 0))
775
+
776
+ all_glyphs.extend(run_glyphs)
777
+ all_positions.extend(run_positions)
778
+
779
+ cf.CFRelease(string)
780
+ cf.CFRelease(attributes)
781
+ cf.CFRelease(cf_str)
782
+ cf.CFRelease(line)
783
+ return all_glyphs, all_positions
784
+
785
+ def get_glyphs(self, text: str, shaping: bool) -> tuple[list[Glyph], list[GlyphPosition]]:
599
786
  """Create and return a list of Glyphs for `text`.
600
787
 
601
788
  If any characters do not have a known glyph representation in this
@@ -604,20 +791,26 @@ class QuartzFont(base.Font):
604
791
  Args:
605
792
  text:
606
793
  Text to render.
794
+ shaping:
795
+ If the text will be shaped using the global option.
607
796
  """
608
797
  self._initialize_renderer()
609
798
 
610
- if pyglet.options.text_shaping == "harfbuzz" and harfbuzz_available():
611
- return get_harfbuzz_shaped_glyphs(self, text)
799
+ if shaping:
800
+ if pyglet.options.text_shaping == "platform":
801
+ return self._get_shaped_glyphs(text)
802
+
803
+ if pyglet.options.text_shaping == "harfbuzz" and harfbuzz_available():
804
+ return get_harfbuzz_shaped_glyphs(self, text)
612
805
 
613
806
  glyphs = [] # glyphs that are committed.
614
807
  offsets = []
615
- for c in base.get_grapheme_clusters(str(text)):
808
+ for c in get_grapheme_clusters(str(text)):
616
809
  if c == "\t":
617
810
  c = " " # noqa: PLW2901
618
811
  if c not in self.glyphs:
619
812
  self.glyphs[c] = self._glyph_renderer.render(c)
620
813
  glyphs.append(self.glyphs[c])
621
- offsets.append(base.GlyphPosition(0, 0, 0, 0))
814
+ offsets.append(GlyphPosition(0, 0, 0, 0))
622
815
 
623
- return glyphs, offsets
816
+ return glyphs, offsets