pyglet 2.1.3__py3-none-any.whl → 2.1.4__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 (61) hide show
  1. pyglet/__init__.py +21 -9
  2. pyglet/__init__.pyi +3 -1
  3. pyglet/app/cocoa.py +6 -3
  4. pyglet/display/win32.py +14 -15
  5. pyglet/display/xlib_vidmoderestore.py +1 -1
  6. pyglet/extlibs/earcut.py +2 -2
  7. pyglet/font/__init__.py +3 -3
  8. pyglet/font/base.py +118 -51
  9. pyglet/font/dwrite/__init__.py +1381 -0
  10. pyglet/font/dwrite/d2d1_lib.py +637 -0
  11. pyglet/font/dwrite/d2d1_types_lib.py +60 -0
  12. pyglet/font/dwrite/dwrite_lib.py +1577 -0
  13. pyglet/font/fontconfig.py +79 -16
  14. pyglet/font/freetype.py +252 -77
  15. pyglet/font/freetype_lib.py +234 -125
  16. pyglet/font/harfbuzz/__init__.py +275 -0
  17. pyglet/font/harfbuzz/harfbuzz_lib.py +212 -0
  18. pyglet/font/quartz.py +432 -112
  19. pyglet/font/user.py +18 -11
  20. pyglet/font/win32.py +9 -1
  21. pyglet/gl/wgl.py +94 -87
  22. pyglet/gl/wglext_arb.py +472 -218
  23. pyglet/gl/wglext_nv.py +410 -188
  24. pyglet/gui/widgets.py +6 -1
  25. pyglet/image/codecs/bmp.py +3 -5
  26. pyglet/image/codecs/gdiplus.py +28 -9
  27. pyglet/image/codecs/wic.py +198 -489
  28. pyglet/image/codecs/wincodec_lib.py +413 -0
  29. pyglet/input/base.py +3 -2
  30. pyglet/input/macos/darwin_hid.py +28 -2
  31. pyglet/input/win32/directinput.py +2 -1
  32. pyglet/input/win32/xinput.py +10 -9
  33. pyglet/lib.py +14 -2
  34. pyglet/libs/darwin/cocoapy/cocoalibs.py +74 -3
  35. pyglet/libs/darwin/coreaudio.py +0 -2
  36. pyglet/libs/win32/__init__.py +4 -2
  37. pyglet/libs/win32/com.py +65 -12
  38. pyglet/libs/win32/constants.py +1 -0
  39. pyglet/libs/win32/dinput.py +1 -9
  40. pyglet/libs/win32/types.py +72 -8
  41. pyglet/media/codecs/coreaudio.py +1 -0
  42. pyglet/media/codecs/wmf.py +93 -72
  43. pyglet/media/devices/win32.py +5 -4
  44. pyglet/media/drivers/directsound/lib_dsound.py +4 -4
  45. pyglet/media/drivers/xaudio2/interface.py +21 -17
  46. pyglet/media/drivers/xaudio2/lib_xaudio2.py +42 -25
  47. pyglet/shapes.py +1 -1
  48. pyglet/text/document.py +7 -53
  49. pyglet/text/formats/attributed.py +3 -1
  50. pyglet/text/formats/plaintext.py +1 -1
  51. pyglet/text/formats/structured.py +1 -1
  52. pyglet/text/layout/base.py +76 -68
  53. pyglet/text/layout/incremental.py +38 -8
  54. pyglet/text/layout/scrolling.py +1 -1
  55. pyglet/text/runlist.py +2 -114
  56. pyglet/window/win32/__init__.py +1 -3
  57. {pyglet-2.1.3.dist-info → pyglet-2.1.4.dist-info}/METADATA +1 -1
  58. {pyglet-2.1.3.dist-info → pyglet-2.1.4.dist-info}/RECORD +60 -54
  59. pyglet/font/directwrite.py +0 -2798
  60. {pyglet-2.1.3.dist-info → pyglet-2.1.4.dist-info}/LICENSE +0 -0
  61. {pyglet-2.1.3.dist-info → pyglet-2.1.4.dist-info}/WHEEL +0 -0
pyglet/font/freetype.py CHANGED
@@ -2,11 +2,12 @@ from __future__ import annotations
2
2
 
3
3
  import warnings
4
4
  from ctypes import POINTER, byref, c_ubyte, cast, memmove
5
- from typing import NamedTuple
5
+ from typing import NamedTuple, Sequence
6
6
 
7
7
  import pyglet
8
8
  from pyglet import image
9
9
  from pyglet.font import base
10
+ from pyglet.font.base import GlyphPosition
10
11
  from pyglet.font.fontconfig import get_fontconfig
11
12
  from pyglet.font.freetype_lib import (
12
13
  FT_LOAD_RENDER,
@@ -14,7 +15,6 @@ from pyglet.font.freetype_lib import (
14
15
  FT_PIXEL_MODE_MONO,
15
16
  FT_STYLE_FLAG_BOLD,
16
17
  FT_STYLE_FLAG_ITALIC,
17
- FreeTypeError,
18
18
  FT_Byte,
19
19
  FT_Done_Face,
20
20
  FT_Face,
@@ -26,8 +26,11 @@ from pyglet.font.freetype_lib import (
26
26
  FT_Set_Char_Size,
27
27
  f26p6_to_float,
28
28
  float_to_f26p6,
29
- ft_get_library,
29
+ ft_get_library, FT_LOAD_TARGET_MONO, FT_LOAD_COLOR, FT_FACE_FLAG_SCALABLE, FT_FACE_FLAG_FIXED_SIZES,
30
+ FT_FACE_FLAG_COLOR, FT_Select_Size, FT_PIXEL_MODE_BGRA, FT_Get_Char_Index, FT_FACE_FLAG_KERNING, FT_Vector,
31
+ FT_Get_Kerning, FT_KERNING_DEFAULT, FT_LOAD_NO_BITMAP, FT_Load_Char
30
32
  )
33
+ from pyglet.font.harfbuzz import harfbuzz_available, get_resource_from_ft_font, get_harfbuzz_shaped_glyphs
31
34
  from pyglet.util import asbytes, asstr
32
35
 
33
36
 
@@ -50,13 +53,17 @@ class FreeTypeGlyphRenderer(base.GlyphRenderer):
50
53
 
51
54
  self._data = None
52
55
 
53
- def _get_glyph(self, character: str) -> None:
56
+ def _get_glyph_by_char(self, character: str) -> None:
54
57
  assert self.font
55
58
  assert len(character) == 1
56
59
 
57
60
  self._glyph_slot = self.font.get_glyph_slot(character)
58
61
  self._bitmap = self._glyph_slot.bitmap
59
62
 
63
+ def _get_glyph_by_index(self, glyph_index: int) -> None:
64
+ self._glyph_slot = self.font.get_glyph_slot_index(glyph_index)
65
+ self._bitmap = self._glyph_slot.bitmap
66
+
60
67
  def _get_glyph_metrics(self) -> None:
61
68
  self._width = self._glyph_slot.bitmap.width
62
69
  self._height = self._glyph_slot.bitmap.rows
@@ -67,6 +74,34 @@ class FreeTypeGlyphRenderer(base.GlyphRenderer):
67
74
  self._lsb = self._glyph_slot.bitmap_left
68
75
  self._advance_x = int(f26p6_to_float(self._glyph_slot.advance.x))
69
76
 
77
+ def _expand_to_rgba(self, data: bytes, src_format: str, dst_format: str) -> bytes:
78
+ """Expands data type to 4 components with putting values into A.
79
+
80
+ Will re-evaluate on a better system later.
81
+ """
82
+ src_len = len(src_format)
83
+ dst_len = len(dst_format)
84
+
85
+ if src_len >= dst_len:
86
+ return data
87
+
88
+ expanded_data = bytearray(len(data) // src_len * dst_len)
89
+ mapping = {c: i for i, c in enumerate(src_format)}
90
+
91
+ for i in range(len(data) // src_len):
92
+ default_value = data[i * src_len + 0] if src_len > 0 else 0
93
+
94
+ for j, c in enumerate(dst_format):
95
+ if c in mapping:
96
+ expanded_data[i * dst_len + j] = 255
97
+ elif c == 'A':
98
+ # Default alpha to fully opaque
99
+ expanded_data[i * dst_len + j] = default_value
100
+ else:
101
+ expanded_data[i * dst_len + j] = 255
102
+
103
+ return bytes(expanded_data)
104
+
70
105
  def _get_bitmap_data(self) -> None:
71
106
  if self._mode == FT_PIXEL_MODE_MONO:
72
107
  # BCF fonts always render to 1 bit mono, regardless of render
@@ -76,59 +111,74 @@ class FreeTypeGlyphRenderer(base.GlyphRenderer):
76
111
  # Usual case
77
112
  assert self._glyph_slot.bitmap.num_grays == 256
78
113
  self._data = self._glyph_slot.bitmap.buffer
114
+ elif self._mode == FT_PIXEL_MODE_BGRA:
115
+ # as of freetype 2.5
116
+ self._data = self._glyph_slot.bitmap.buffer
79
117
  else:
80
118
  msg = "Unsupported render mode for this glyph"
81
119
  raise base.FontException(msg)
82
120
 
83
121
  def _convert_mono_to_gray_bitmap(self) -> None:
84
- bitmap_data = cast(self._bitmap.buffer,
85
- POINTER(c_ubyte * (self._pitch * self._height))).contents
86
- data = (c_ubyte * (self._pitch * 8 * self._height))()
87
- data_i = 0
88
- for byte in bitmap_data:
89
- # Data is MSB; left-most pixel in a byte has value 128.
90
- data[data_i + 0] = (byte & 0x80) and 255 or 0
91
- data[data_i + 1] = (byte & 0x40) and 255 or 0
92
- data[data_i + 2] = (byte & 0x20) and 255 or 0
93
- data[data_i + 3] = (byte & 0x10) and 255 or 0
94
- data[data_i + 4] = (byte & 0x08) and 255 or 0
95
- data[data_i + 5] = (byte & 0x04) and 255 or 0
96
- data[data_i + 6] = (byte & 0x02) and 255 or 0
97
- data[data_i + 7] = (byte & 0x01) and 255 or 0
98
- data_i += 8
122
+ if self._bitmap.buffer:
123
+ bitmap_data = cast(self._bitmap.buffer,
124
+ POINTER(c_ubyte * (self._pitch * self._height))).contents
125
+
126
+ data = (c_ubyte * (self._width * self._height))()
127
+
128
+ # Tightly pack the data, as freetype pads it.
129
+ for y in range(self._height):
130
+ for x in range(self._width):
131
+ byte = bitmap_data[y * self._pitch + (x // 8)]
132
+ bit = 7 - (x % 8) # Data is MSB; left-most pixel in a byte has value 128.
133
+ data[y * self._width + x] = 255 if (byte & (1 << bit)) else 0
134
+ else:
135
+ # No pointer in the buffer, no default or fallback in this font.
136
+ data = (c_ubyte * 0)()
99
137
  self._data = data
100
- self._pitch <<= 3
138
+ self._pitch = self._width
101
139
 
102
140
  def _create_glyph(self) -> base.Glyph:
103
- # In FT positive pitch means `down` flow, in Pyglet ImageData
104
- # negative values indicate a top-to-bottom arrangement. So pitch must be inverted.
105
- # Using negative pitch causes conversions, so much faster to just swap tex_coords
106
- img = image.ImageData(self._width,
107
- self._height,
108
- "A",
109
- self._data,
110
- abs(self._pitch))
111
-
112
- # HACK: Get text working in GLES until image data can be converted properly
113
- # GLES don't support coversion during pixel transfer so we have to
114
- # force specify the glyph format to be GL_ALPHA. This format is not
115
- # supported in 3.3+ core, but are present in ES because of pixel transfer
116
- # limitations.
117
- if pyglet.gl.current_context.get_info().get_opengl_api() == "gles":
118
- GL_ALPHA = 0x1906
119
- glyph = self.font.create_glyph(img, fmt=GL_ALPHA)
141
+ # Textures should be a minimum of 1x1.
142
+ if self._width == 0 and self._height == 0:
143
+ width = 1
144
+ height = 1
145
+ data = bytes(bytearray([0, 0, 0, 0]))
146
+ pitch = 4
120
147
  else:
121
- glyph = self.font.create_glyph(img)
122
-
148
+ width = self._width
149
+ height = self._height
150
+ # If it's not in BGRA, convert it manually.
151
+ if self._mode != FT_PIXEL_MODE_BGRA:
152
+ size = self._width * self._height
153
+ ptr = cast(self._data, POINTER(c_ubyte * size))
154
+ data = self._expand_to_rgba(ptr.contents, 'R', 'BGRA')
155
+ pitch = self._pitch * 4
156
+ else:
157
+ data = self._data
158
+ pitch = self._pitch
159
+
160
+ img = image.ImageData(width, height, "BGRA", data, pitch)
161
+
162
+ glyph = self.font.create_glyph(img)
123
163
  glyph.set_bearings(self._baseline, self._lsb, self._advance_x)
164
+
165
+ # In FT positive pitch means `down` flow, in Pyglet ImageData
166
+ # negative values indicate a top-to-bottom arrangement. So pitch must be inverted.
167
+ # Using negative pitch causes a CPU re-ordering. For now, swap texture coordinates for speed.
124
168
  if self._pitch > 0:
125
169
  t = list(glyph.tex_coords)
126
170
  glyph.tex_coords = t[9:12] + t[6:9] + t[3:6] + t[:3]
127
171
 
128
172
  return glyph
129
173
 
174
+ def render_index(self, glyph_index: int):
175
+ self._get_glyph_by_index(glyph_index)
176
+ self._get_glyph_metrics()
177
+ self._get_bitmap_data()
178
+ return self._create_glyph()
179
+
130
180
  def render(self, text: str) -> base.Glyph:
131
- self._get_glyph(text[0])
181
+ self._get_glyph_by_char(text[0])
132
182
  self._get_glyph_metrics()
133
183
  self._get_bitmap_data()
134
184
  return self._create_glyph()
@@ -159,27 +209,30 @@ class MemoryFaceStore:
159
209
 
160
210
  class FreeTypeFont(base.Font):
161
211
  glyph_renderer_class = FreeTypeGlyphRenderer
212
+ _glyph_renderer: FreeTypeGlyphRenderer
162
213
 
163
214
  # Map font (name, weight, italic) to FreeTypeMemoryFace
164
215
  _memory_faces = MemoryFaceStore()
165
216
  face: FreeTypeFace
217
+ fallbacks: list[FreeTypeFont]
166
218
 
167
- def __init__(self, name: str, size: float, weight: str = "normal", italic: bool = False, stretch: bool = False,
168
- dpi: int | None = None) -> None:
169
-
170
- if stretch:
171
- warnings.warn("The current font render does not support stretching.") # noqa: B028
172
-
219
+ def __init__(self, name: str, size: float, weight: str = "normal", italic: bool = False,
220
+ stretch: bool | str = False, dpi: int | None = None) -> None:
173
221
  super().__init__()
174
222
  self._name = name
175
223
  self.size = size
176
224
  self.weight = weight
177
225
  self.italic = italic
226
+ self.stretch = stretch
178
227
  self.dpi = dpi or 96
228
+ self.pixel_size = (self.size * self.dpi) // 72
179
229
 
180
230
  self._load_font_face()
181
231
  self.metrics = self.face.get_font_metrics(self.size, self.dpi)
182
232
 
233
+ if pyglet.options.text_shaping == 'harfbuzz' and harfbuzz_available():
234
+ self.hb_resource = get_resource_from_ft_font(self)
235
+
183
236
  @property
184
237
  def name(self) -> str:
185
238
  return self.face.family_name
@@ -192,8 +245,32 @@ class FreeTypeFont(base.Font):
192
245
  def descent(self) -> int:
193
246
  return self.metrics.descent
194
247
 
248
+ def add_fallback(self, font):
249
+ self.fallbacks.append(font)
250
+
251
+ def _get_slot_from_fallbacks(self, character: str) -> FT_GlyphSlot | None:
252
+ """Checks all fallback fonts in order to find a valid glyph index."""
253
+ # Check if fallback has this glyph, if so.
254
+ for fallback_font in self.fallbacks:
255
+ fb_index = fallback_font.face.get_character_index(character)
256
+ if fb_index:
257
+ fallback_font.face.set_char_size(self.size, self.dpi)
258
+ return fallback_font.get_glyph_slot(character)
259
+
260
+ return None
261
+
262
+ def get_glyph_slot_index(self, glyph_index: int) -> FT_GlyphSlot:
263
+ self.face.set_char_size(self.size, self.dpi)
264
+ return self.face.get_glyph_slot(glyph_index)
265
+
195
266
  def get_glyph_slot(self, character: str) -> FT_GlyphSlot:
196
267
  glyph_index = self.face.get_character_index(character)
268
+ # Glyph index does not exist, so check fallback fonts.
269
+ if glyph_index == 0 and (self.name not in self.fallbacks):
270
+ glyph_slot = self._get_slot_from_fallbacks(character)
271
+ if glyph_slot is not None:
272
+ return glyph_slot
273
+
197
274
  self.face.set_char_size(self.size, self.dpi)
198
275
  return self.face.get_glyph_slot(glyph_index)
199
276
 
@@ -203,7 +280,7 @@ class FreeTypeFont(base.Font):
203
280
  self._load_font_face_from_system()
204
281
 
205
282
  def _load_font_face_from_system(self) -> None:
206
- match = get_fontconfig().find_font(self._name, self.size, self.weight, self.italic)
283
+ match = get_fontconfig().find_font(self._name, self.size, self.weight, self.italic, self.stretch)
207
284
  if not match:
208
285
  msg = f"Could not match font '{self._name}'"
209
286
  raise base.FontException(msg)
@@ -219,9 +296,80 @@ class FreeTypeFont(base.Font):
219
296
 
220
297
  @classmethod
221
298
  def add_font_data(cls: type[FreeTypeFont], data: bytes) -> None:
222
- face = FreeTypeMemoryFace(data)
299
+ font_data = (FT_Byte * len(data))()
300
+ memmove(font_data, data, len(data))
301
+
302
+ face = FreeTypeMemoryFace(font_data, 0)
223
303
  cls._memory_faces.add(face)
304
+ count = face.face_count
305
+ # Some fonts may be a collection. Load each one.
306
+ if count > 1:
307
+ for i in range(1, count):
308
+ face = FreeTypeMemoryFace(font_data, i)
309
+ cls._memory_faces.add(face)
310
+
311
+ def render_glyph_indices(self, indices: Sequence[int]):
312
+ # Process any glyphs that have not been rendered.
313
+ self._initialize_renderer()
314
+
315
+ missing = set()
316
+ for glyph_indice in set(indices):
317
+ if glyph_indice not in self.glyphs:
318
+ missing.add(glyph_indice)
319
+
320
+ # Missing glyphs, get their info.
321
+ for glyph_indice in missing:
322
+ self.glyphs[glyph_indice] = self._glyph_renderer.render_index(glyph_indice)
323
+
324
+ def get_glyphs(self, text: str) -> tuple[list[base.Glyph], list[base.GlyphPosition]]:
325
+ """Create and return a list of Glyphs for `text`.
326
+
327
+ If any characters do not have a known glyph representation in this
328
+ font, a substitution will be made.
329
+
330
+ Args:
331
+ text:
332
+ Text to render.
333
+ """
334
+ self._initialize_renderer()
335
+ if pyglet.options.text_shaping == "harfbuzz" and harfbuzz_available():
336
+ return get_harfbuzz_shaped_glyphs(self, text)
337
+ else:
338
+ glyphs = [] # glyphs that are committed.
339
+ for idx, c in enumerate(text):
340
+ # Get the glyph for 'c'. Hide tabs (Windows and Linux render
341
+ # boxes)
342
+ if c == "\t":
343
+ c = " " # noqa: PLW2901
344
+ if c not in self.glyphs:
345
+ self.glyphs[c] = self._glyph_renderer.render(c)
346
+ glyphs.append(self.glyphs[c])
347
+
348
+ return glyphs, [GlyphPosition(0, 0, 0, 0)] * len(text)
349
+
350
+ def get_text_size(self, text: str) -> tuple[int, int]:
351
+ width = 0
352
+ max_top = 0
353
+ min_bottom = 0
354
+ length = len(text)
355
+ self.face.set_char_size(self.size, self.dpi)
356
+ for i, char in enumerate(text):
357
+ FT_Load_Char(self.face.ft_face, ord(char), FT_LOAD_NO_BITMAP)
358
+ slot = self.face.ft_face.contents.glyph.contents
359
+
360
+ if i == length-1:
361
+ # Last glyph, use just the width.
362
+ width += slot.metrics.width >> 6
363
+ else:
364
+ width += slot.advance.x >> 6
224
365
 
366
+ glyph_top = slot.metrics.horiBearingY >> 6
367
+ glyph_bottom = (slot.metrics.horiBearingY - slot.metrics.height) >> 6
368
+
369
+ max_top = max(max_top, glyph_top)
370
+ min_bottom = min(min_bottom, glyph_bottom)
371
+
372
+ return width, max_top - min_bottom
225
373
 
226
374
  class FreeTypeFace:
227
375
  """FreeType typographic face object.
@@ -238,6 +386,18 @@ class FreeTypeFace:
238
386
  self.ft_face = ft_face
239
387
  self._get_best_name()
240
388
 
389
+ self._italic = self.style_flags & FT_STYLE_FLAG_ITALIC != 0
390
+ bold = self.style_flags & FT_STYLE_FLAG_BOLD != 0
391
+ if bold:
392
+ self._weight = "bold"
393
+ else:
394
+ # Sometimes it may have a weight, but FT_STYLE_FLAG_BOLD is not accurate. Check the font config.
395
+ config = get_fontconfig()
396
+ self._weight, italic, self._stretch = config.style_from_face(self.ft_face)
397
+ if italic != self._italic:
398
+ # Discrepancy in italics?
399
+ self._italic = italic
400
+
241
401
  @classmethod
242
402
  def from_file(cls: type[FreeTypeFace], file_name: str) -> FreeTypeFace:
243
403
  ft_library = ft_get_library()
@@ -267,6 +427,10 @@ class FreeTypeFace:
267
427
  def family_name(self) -> str:
268
428
  return asstr(self.ft_face.contents.family_name)
269
429
 
430
+ @property
431
+ def style_name(self) -> str:
432
+ return asstr(self.ft_face.contents.style_name)
433
+
270
434
  @property
271
435
  def style_flags(self) -> int:
272
436
  return self.ft_face.contents.style_flags
@@ -277,11 +441,11 @@ class FreeTypeFace:
277
441
 
278
442
  @property
279
443
  def weight(self) -> str:
280
- return "normal"
444
+ return self._weight
281
445
 
282
446
  @property
283
447
  def italic(self) -> bool:
284
- return self.style_flags & FT_STYLE_FLAG_ITALIC != 0
448
+ return self._italic
285
449
 
286
450
  @property
287
451
  def face_flags(self) -> int:
@@ -293,27 +457,39 @@ class FreeTypeFace:
293
457
  self.ft_face = None
294
458
 
295
459
  def set_char_size(self, size: float, dpi: int) -> bool:
296
- face_size = float_to_f26p6(size)
297
- try:
298
- FT_Set_Char_Size(self.ft_face,
299
- 0,
300
- face_size,
301
- dpi,
302
- dpi)
303
- return True
304
- except FreeTypeError as e:
305
- # Error 0x17 indicates invalid pixel size, so font size cannot be changed
306
- # TODO Warn the user?
307
- if e.errcode == 0x17:
460
+ if self.face_flags & FT_FACE_FLAG_SCALABLE:
461
+ face_size = float_to_f26p6(size)
462
+ FT_Set_Char_Size(self.ft_face, 0, face_size, dpi, dpi)
463
+
464
+ elif self.face_flags & FT_FACE_FLAG_COLOR:
465
+ if FT_Select_Size:
466
+ FT_Select_Size(self.ft_face, 0)
467
+ else:
308
468
  return False
309
469
 
310
- raise
470
+ elif self.face_flags & FT_FACE_FLAG_FIXED_SIZES:
471
+ if self.ft_face.contents.num_fixed_sizes:
472
+ if FT_Select_Size:
473
+ FT_Select_Size(self.ft_face, 0)
474
+ else:
475
+ return False
476
+ else:
477
+ warnings.warn(f"{self.name} no fixed sizes, but is flagged as a fixed size font.")
478
+
479
+ return True
311
480
 
312
481
  def get_character_index(self, character: str) -> int:
313
- return get_fontconfig().char_index(self.ft_face, character)
482
+ return FT_Get_Char_Index(self.ft_face, ord(character))
314
483
 
315
484
  def get_glyph_slot(self, glyph_index: int) -> FT_GlyphSlot:
316
- FT_Load_Glyph(self.ft_face, glyph_index, FT_LOAD_RENDER)
485
+ flags = FT_LOAD_RENDER
486
+ if pyglet.options.text_antialiasing is False:
487
+ flags |= FT_LOAD_TARGET_MONO
488
+
489
+ if self.face_flags & FT_FACE_FLAG_COLOR:
490
+ flags |= FT_LOAD_COLOR
491
+
492
+ FT_Load_Glyph(self.ft_face, glyph_index, flags)
317
493
  return self.ft_face.contents.glyph.contents
318
494
 
319
495
  def get_font_metrics(self, size: float, dpi: int) -> FreeTypeFontMetrics:
@@ -323,7 +499,8 @@ class FreeTypeFace:
323
499
  return self._get_font_metrics_workaround()
324
500
 
325
501
  return FreeTypeFontMetrics(ascent=int(f26p6_to_float(metrics.ascender)),
326
- descent=int(f26p6_to_float(metrics.descender)))
502
+ descent=int(f26p6_to_float(metrics.descender)),
503
+ )
327
504
 
328
505
  return self._get_font_metrics_workaround()
329
506
 
@@ -366,20 +543,18 @@ class FreeTypeFace:
366
543
 
367
544
 
368
545
  class FreeTypeMemoryFace(FreeTypeFace):
369
- def __init__(self, data: bytes) -> None:
370
- self._copy_font_data(data)
371
- super().__init__(self._create_font_face())
372
-
373
- def _copy_font_data(self, data: bytes) -> None:
374
- self.font_data = (FT_Byte * len(data))()
375
- memmove(self.font_data, data, len(data))
376
-
377
- def _create_font_face(self) -> FT_Face:
546
+ def __init__(self, data: bytes, face_index: int = 0) -> None:
547
+ self.font_data = data
378
548
  ft_library = ft_get_library()
379
549
  ft_face = FT_Face()
380
550
  FT_New_Memory_Face(ft_library,
381
551
  self.font_data,
382
552
  len(self.font_data),
383
- 0,
553
+ face_index,
384
554
  byref(ft_face))
385
- return ft_face
555
+
556
+ super().__init__(ft_face)
557
+
558
+ @property
559
+ def face_count(self) -> int:
560
+ return self.ft_face.contents.num_faces