pyglet 2.1.2__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 (74) hide show
  1. pyglet/__init__.py +21 -9
  2. pyglet/__init__.pyi +3 -1
  3. pyglet/app/cocoa.py +6 -3
  4. pyglet/app/xlib.py +1 -1
  5. pyglet/display/cocoa.py +2 -2
  6. pyglet/display/win32.py +17 -18
  7. pyglet/display/xlib.py +2 -2
  8. pyglet/display/xlib_vidmoderestore.py +1 -1
  9. pyglet/extlibs/earcut.py +2 -2
  10. pyglet/font/__init__.py +3 -3
  11. pyglet/font/base.py +118 -51
  12. pyglet/font/dwrite/__init__.py +1381 -0
  13. pyglet/font/dwrite/d2d1_lib.py +637 -0
  14. pyglet/font/dwrite/d2d1_types_lib.py +60 -0
  15. pyglet/font/dwrite/dwrite_lib.py +1577 -0
  16. pyglet/font/fontconfig.py +79 -16
  17. pyglet/font/freetype.py +252 -77
  18. pyglet/font/freetype_lib.py +234 -125
  19. pyglet/font/harfbuzz/__init__.py +275 -0
  20. pyglet/font/harfbuzz/harfbuzz_lib.py +212 -0
  21. pyglet/font/quartz.py +432 -112
  22. pyglet/font/user.py +18 -11
  23. pyglet/font/win32.py +9 -1
  24. pyglet/gl/wgl.py +94 -87
  25. pyglet/gl/wglext_arb.py +472 -218
  26. pyglet/gl/wglext_nv.py +410 -188
  27. pyglet/gui/frame.py +4 -4
  28. pyglet/gui/widgets.py +6 -1
  29. pyglet/image/__init__.py +0 -2
  30. pyglet/image/codecs/bmp.py +3 -5
  31. pyglet/image/codecs/dds.py +1 -1
  32. pyglet/image/codecs/gdiplus.py +28 -9
  33. pyglet/image/codecs/wic.py +198 -489
  34. pyglet/image/codecs/wincodec_lib.py +413 -0
  35. pyglet/input/base.py +3 -2
  36. pyglet/input/linux/x11_xinput.py +3 -3
  37. pyglet/input/linux/x11_xinput_tablet.py +2 -2
  38. pyglet/input/macos/darwin_hid.py +28 -2
  39. pyglet/input/win32/directinput.py +3 -2
  40. pyglet/input/win32/wintab.py +1 -1
  41. pyglet/input/win32/xinput.py +10 -9
  42. pyglet/lib.py +14 -2
  43. pyglet/libs/darwin/cocoapy/cocoalibs.py +74 -3
  44. pyglet/libs/darwin/coreaudio.py +0 -2
  45. pyglet/libs/win32/__init__.py +4 -2
  46. pyglet/libs/win32/com.py +65 -12
  47. pyglet/libs/win32/constants.py +1 -0
  48. pyglet/libs/win32/dinput.py +1 -9
  49. pyglet/libs/win32/types.py +72 -8
  50. pyglet/math.py +5 -5
  51. pyglet/media/codecs/coreaudio.py +1 -0
  52. pyglet/media/codecs/wmf.py +93 -72
  53. pyglet/media/devices/win32.py +5 -4
  54. pyglet/media/drivers/directsound/lib_dsound.py +4 -4
  55. pyglet/media/drivers/xaudio2/interface.py +21 -17
  56. pyglet/media/drivers/xaudio2/lib_xaudio2.py +42 -25
  57. pyglet/model/__init__.py +78 -57
  58. pyglet/shapes.py +1 -1
  59. pyglet/text/document.py +7 -53
  60. pyglet/text/formats/attributed.py +3 -1
  61. pyglet/text/formats/plaintext.py +1 -1
  62. pyglet/text/formats/structured.py +1 -1
  63. pyglet/text/layout/base.py +76 -68
  64. pyglet/text/layout/incremental.py +38 -8
  65. pyglet/text/layout/scrolling.py +1 -1
  66. pyglet/text/runlist.py +2 -114
  67. pyglet/window/__init__.py +11 -8
  68. pyglet/window/win32/__init__.py +1 -3
  69. pyglet/window/xlib/__init__.py +2 -2
  70. {pyglet-2.1.2.dist-info → pyglet-2.1.4.dist-info}/METADATA +2 -3
  71. {pyglet-2.1.2.dist-info → pyglet-2.1.4.dist-info}/RECORD +73 -67
  72. pyglet/font/directwrite.py +0 -2798
  73. {pyglet-2.1.2.dist-info → pyglet-2.1.4.dist-info}/LICENSE +0 -0
  74. {pyglet-2.1.2.dist-info → pyglet-2.1.4.dist-info}/WHEEL +0 -0
@@ -1,30 +1,47 @@
1
- import ctypes
2
- import platform
3
1
  import os
4
- from pyglet.libs.win32.constants import *
5
- from pyglet.libs.win32.types import *
2
+ import platform
3
+ from ctypes import (
4
+ HRESULT,
5
+ POINTER,
6
+ Structure,
7
+ c_bool,
8
+ c_char,
9
+ c_float,
10
+ c_int,
11
+ c_uint32,
12
+ c_uint64,
13
+ c_void_p,
14
+ cdll,
15
+ windll,
16
+ )
17
+ from ctypes.wintypes import BOOL, BYTE, DWORD, FLOAT, LPCWSTR, UINT, WORD
18
+
6
19
  from pyglet.libs.win32 import com
20
+ from pyglet.libs.win32.constants import WINDOWS_10_ANNIVERSARY_UPDATE_OR_GREATER
21
+ from pyglet.libs.win32.types import c_void
7
22
  from pyglet.util import debug_print
8
23
 
9
24
  _debug = debug_print('debug_media')
10
25
 
11
26
 
12
27
  def load_xaudio2(dll_name):
13
- """This will attempt to load a version of XAudio2. Versions supported: 2.9, 2.8.
14
- While Windows 8 ships with 2.8 and Windows 10 ships with version 2.9, it is possible to install 2.9 on 8/8.1.
28
+ """This will attempt to load a version of XAudio2.
29
+
30
+ Versions supported: 2.9, 2.8.
31
+
32
+ While Windows 8 ships with 2.8 and Windows 10 ships with version 2.9, it is possible to install 2.9 on 8/8.1.
15
33
  """
16
34
  xaudio2 = dll_name
17
35
  # System32 and SysWOW64 folders are opposite perception in Windows x64.
18
36
  # System32 = x64 dll's | SysWOW64 = x86 dlls
19
37
  # By default ctypes only seems to look in system32 regardless of Python architecture, which has x64 dlls.
20
- if platform.architecture()[0] == '32bit':
21
- if platform.machine().endswith('64'): # Machine is 64 bit, Python is 32 bit.
22
- xaudio2 = os.path.join(os.environ['WINDIR'], 'SysWOW64', '{}.dll'.format(xaudio2))
38
+ if platform.architecture()[0] == '32bit' and platform.machine().endswith('64'): # Machine is 64 bit, Python is 32 bit.
39
+ xaudio2 = os.path.join(os.environ['WINDIR'], 'SysWOW64', '{}.dll'.format(xaudio2))
23
40
 
24
- xaudio2_lib = ctypes.windll.LoadLibrary(xaudio2)
41
+ xaudio2_lib = windll.LoadLibrary(xaudio2)
25
42
 
26
43
  # Somehow x3d uses different calling structure than the rest of the DLL; Only affects 32 bit? Microsoft...
27
- x3d_lib = ctypes.cdll.LoadLibrary(xaudio2)
44
+ x3d_lib = cdll.LoadLibrary(xaudio2)
28
45
  return xaudio2_lib, x3d_lib
29
46
 
30
47
 
@@ -43,7 +60,7 @@ UINT32 = c_uint32
43
60
  FLOAT32 = c_float
44
61
 
45
62
 
46
- class XAUDIO2_DEBUG_CONFIGURATION(ctypes.Structure):
63
+ class XAUDIO2_DEBUG_CONFIGURATION(Structure):
47
64
  _fields_ = [
48
65
  ('TraceMask', UINT32),
49
66
  ('BreakMask', UINT32),
@@ -54,7 +71,7 @@ class XAUDIO2_DEBUG_CONFIGURATION(ctypes.Structure):
54
71
  ]
55
72
 
56
73
 
57
- class XAUDIO2_PERFORMANCE_DATA(ctypes.Structure):
74
+ class XAUDIO2_PERFORMANCE_DATA(Structure):
58
75
  _fields_ = [
59
76
  ('AudioCyclesSinceLastQuery', c_uint64),
60
77
  ('TotalCyclesSinceLastQuery', c_uint64),
@@ -76,14 +93,14 @@ class XAUDIO2_PERFORMANCE_DATA(ctypes.Structure):
76
93
  return "XAUDIO2PerformanceData(active_voices={}, total_voices={}, glitches={}, latency={} samples, memory_usage={} bytes)".format(self.ActiveSourceVoiceCount, self.TotalSourceVoiceCount, self.GlitchesSinceEngineStarted, self.CurrentLatencyInSamples, self.MemoryUsageInBytes)
77
94
 
78
95
 
79
- class XAUDIO2_VOICE_SENDS(ctypes.Structure):
96
+ class XAUDIO2_VOICE_SENDS(Structure):
80
97
  _fields_ = [
81
98
  ('SendCount', UINT32),
82
99
  ('pSends', c_void_p),
83
100
  ]
84
101
 
85
102
 
86
- class XAUDIO2_BUFFER(ctypes.Structure):
103
+ class XAUDIO2_BUFFER(Structure):
87
104
  _fields_ = [
88
105
  ('Flags', UINT32),
89
106
  ('AudioBytes', UINT32),
@@ -96,7 +113,7 @@ class XAUDIO2_BUFFER(ctypes.Structure):
96
113
  ('pContext', c_void_p),
97
114
  ]
98
115
 
99
- class XAUDIO2_VOICE_STATE(ctypes.Structure):
116
+ class XAUDIO2_VOICE_STATE(Structure):
100
117
  _fields_ = [
101
118
  ('pCurrentBufferContext', c_void_p),
102
119
  ('BuffersQueued', UINT32),
@@ -106,7 +123,7 @@ class XAUDIO2_VOICE_STATE(ctypes.Structure):
106
123
  def __repr__(self):
107
124
  return "XAUDIO2_VOICE_STATE(BuffersQueued={0}, SamplesPlayed={1})".format(self.BuffersQueued, self.SamplesPlayed)
108
125
 
109
- class WAVEFORMATEX(ctypes.Structure):
126
+ class WAVEFORMATEX(Structure):
110
127
  _fields_ = [
111
128
  ('wFormatTag', WORD),
112
129
  ('nChannels', WORD),
@@ -198,13 +215,13 @@ class IXAudio2VoiceCallback(com.Interface):
198
215
  ('OnStreamEnd',
199
216
  com.VOIDMETHOD()),
200
217
  ('OnBufferStart',
201
- com.VOIDMETHOD(ctypes.c_void_p)),
218
+ com.VOIDMETHOD(c_void_p)),
202
219
  ('OnBufferEnd',
203
- com.VOIDMETHOD(ctypes.c_void_p)),
220
+ com.VOIDMETHOD(c_void_p)),
204
221
  ('OnLoopEnd',
205
- com.VOIDMETHOD(ctypes.c_void_p)),
222
+ com.VOIDMETHOD(c_void_p)),
206
223
  ('OnVoiceError',
207
- com.VOIDMETHOD(ctypes.c_void_p, HRESULT))
224
+ com.VOIDMETHOD(c_void_p, HRESULT))
208
225
  ]
209
226
 
210
227
 
@@ -216,7 +233,7 @@ class XAUDIO2_EFFECT_DESCRIPTOR(Structure):
216
233
  ]
217
234
 
218
235
 
219
- class XAUDIO2_EFFECT_CHAIN(ctypes.Structure):
236
+ class XAUDIO2_EFFECT_CHAIN(Structure):
220
237
  _fields_ = [
221
238
  ('EffectCount', UINT32),
222
239
  ('pEffectDescriptors', POINTER(XAUDIO2_EFFECT_DESCRIPTOR)),
@@ -331,21 +348,21 @@ class IXAudio2EngineCallback(com.Interface):
331
348
 
332
349
 
333
350
  # -------------- 3D Audio Positioning----------
334
- class X3DAUDIO_DISTANCE_CURVE_POINT(ctypes.Structure):
351
+ class X3DAUDIO_DISTANCE_CURVE_POINT(Structure):
335
352
  _fields_ = [
336
353
  ('Distance', FLOAT32),
337
354
  ('DSPSetting', FLOAT32)
338
355
  ]
339
356
 
340
357
 
341
- class X3DAUDIO_DISTANCE_CURVE(ctypes.Structure):
358
+ class X3DAUDIO_DISTANCE_CURVE(Structure):
342
359
  _fields_ = [
343
360
  ('pPoints', POINTER(X3DAUDIO_DISTANCE_CURVE_POINT)),
344
361
  ('PointCount', UINT32)
345
362
  ]
346
363
 
347
364
 
348
- class X3DAUDIO_VECTOR(ctypes.Structure):
365
+ class X3DAUDIO_VECTOR(Structure):
349
366
  _fields_ = [
350
367
  ('x', c_float),
351
368
  ('y', c_float),
pyglet/model/__init__.py CHANGED
@@ -37,7 +37,7 @@ from pyglet.math import Mat4
37
37
 
38
38
  from .codecs import add_default_codecs as _add_default_codecs
39
39
  from .codecs import registry as _codec_registry
40
- from .codecs.base import Material, Scene
40
+ from .codecs.base import Material, Scene, SimpleMaterial
41
41
 
42
42
  if TYPE_CHECKING:
43
43
  from typing import BinaryIO, TextIO
@@ -137,22 +137,13 @@ class Model:
137
137
  for group in self.groups:
138
138
  group.matrix = matrix
139
139
 
140
- def draw(self) -> None:
141
- """Draw the model.
142
-
143
- This is not recommended. See the module documentation
144
- for information on efficient drawing of multiple models.
145
- """
146
- gl.current_context.window_block.bind(0)
147
- self._batch.draw_subset(self.vertex_lists)
148
-
149
140
 
150
141
  class BaseMaterialGroup(graphics.Group):
151
142
  default_vert_src: str
152
143
  default_frag_src: str
153
144
  matrix: Mat4 = Mat4()
154
145
 
155
- def __init__(self, material: Material, program: ShaderProgram, order: int = 0, parent: Group | None = None) -> None:
146
+ def __init__(self, material: SimpleMaterial, program: ShaderProgram, order: int = 0, parent: Group | None = None) -> None:
156
147
  super().__init__(order, parent)
157
148
  self.material = material
158
149
  self.program = program
@@ -209,7 +200,7 @@ class TexturedMaterialGroup(BaseMaterialGroup):
209
200
  }
210
201
  """
211
202
 
212
- def __init__(self, material: Material, program: ShaderProgram,
203
+ def __init__(self, material: SimpleMaterial, program: ShaderProgram,
213
204
  texture: Texture, order: int = 0, parent: Group | None = None):
214
205
  super().__init__(material, program, order, parent)
215
206
  self.texture = texture
@@ -305,7 +296,7 @@ class Cube(Model):
305
296
  self._program = program if program else get_default_shader()
306
297
 
307
298
  # Create a Material and Group for the Model
308
- self._material = material if material else pyglet.model.Material(name="cube")
299
+ self._material = material if material else SimpleMaterial(name="cube")
309
300
  self._group = pyglet.model.MaterialGroup(material=self._material, program=self._program, parent=group)
310
301
 
311
302
  self._vlist = self._create_vertexlist()
@@ -317,30 +308,51 @@ class Cube(Model):
317
308
  h = self._height / 2
318
309
  d = self._depth / 2
319
310
 
320
- vertices = (-w, -h, -d, # front, bottom-left 0
321
- w, -h, -d, # front, bottom-right 1
322
- w, h, -d, # front, top-right 2
323
- -w, h, -d, # front, top-left 3
324
- -w, -h, d, # back, bottom-left 4
325
- w, -h, d, # back, bottom-right 5
326
- w, h, d, # back, top-right 6
327
- -w, h, d) # back, top-left 7
328
-
329
- normals = (-0.5, -0.5, -1.0, # back, bottom-left
330
- 0.5, -0.5, -1.0, # back, buttom-right
331
- 0.5, 0.5, -1.0, # back, top-right
332
- -0.5, 0.5, -1.0, # back, top-left
333
- -0.5, -0.5, 0.0, # front, bottom-left
334
- 0.5, -0.5, 0.0, # front, bottom-right
335
- 0.5, 0.5, 0.0, # front, top-right
336
- -0.5, 0.5, 0.0) # front, top-left
337
-
338
- indices = (0, 3, 2, 0, 2, 1, # front
339
- 4, 5, 6, 4, 6, 7, # back
340
- 4, 7, 3, 4, 3, 0, # left
341
- 1, 2, 6, 1, 6, 5, # right
342
- 3, 7, 6, 3, 6, 2, # top
343
- 0, 1, 5, 0, 5, 4) # bottom
311
+ vertices = [
312
+ -w, -h, -d, # front, bottom-left 0
313
+ w, -h, -d, # front, bottom-right 1
314
+ w, h, -d, # front, top-right 2 Front
315
+ -w, h, -d, # front, top-left 3
316
+
317
+ w, -h, d, # back, bottom-right 4
318
+ -w, -h, d, # back, bottom-left 5
319
+ -w, h, d, # back, top-left 6 Back
320
+ w, h, d, # back, top-right 7
321
+
322
+ w, -h, -d, # front, bottom-right 8
323
+ w, -h, d, # back, bottom-right 9
324
+ w, h, d, # back, top-right 10 Right
325
+ w, h, -d, # front, top-right 11
326
+
327
+ -w, -h, d, # back, bottom-left 12
328
+ -w, -h, -d, # front, bottom-left 13
329
+ -w, h, -d, # front, top-left 14 Left
330
+ -w, h, d, # back, top-left 15
331
+
332
+ -w, h, -d, # front, top-left 16
333
+ w, h, -d, # front, top-right 17
334
+ w, h, d, # back, top-right 18 Top
335
+ -w, h, d, # back, top-left 19
336
+
337
+ -w, -h, d, # back, bottom-left 20
338
+ w, -h, d, # back, bottom-right 21
339
+ w, -h, -d, # front, bottom-right 22 Bottom
340
+ -w, -h, -d, # front, bottom-left 23
341
+ ]
342
+
343
+ normals = [0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, # front face
344
+ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, # back face
345
+ 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, # right face
346
+ -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, # left face
347
+ 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, # top face
348
+ 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0] # bottom face
349
+
350
+ indices = [23, 22, 20, 22, 21, 20, # bottom
351
+ 19, 18, 16, 18, 17, 16, # top
352
+ 15, 14, 12, 14, 13, 12, # left
353
+ 11, 10, 8, 10, 9, 8, # right
354
+ 7, 6, 4, 6, 5, 4, # back
355
+ 3, 2, 0, 2, 1, 0] # front
344
356
 
345
357
  return self._program.vertex_list_indexed(len(vertices) // 3, pyglet.gl.GL_TRIANGLES, indices,
346
358
  batch=self._batch, group=self._group,
@@ -351,17 +363,18 @@ class Cube(Model):
351
363
 
352
364
  class Sphere(Model):
353
365
 
354
- def __init__(self, radius=1.0, segments=30, color=(1.0, 1.0, 1.0, 1.0),
366
+ def __init__(self, radius=1.0, stacks=30, sectors=30, color=(1.0, 1.0, 1.0, 1.0),
355
367
  material=None, batch=None, group=None, program=None):
356
368
  self._radius = radius
357
- self._segments = segments
369
+ self._stacks = stacks
370
+ self._sectors = sectors
358
371
  self._color = color
359
372
 
360
373
  self._batch = batch
361
374
  self._program = program if program else get_default_shader()
362
375
 
363
376
  # Create a Material and Group for the Model
364
- self._material = material if material else pyglet.model.Material(name="sphere")
377
+ self._material = material if material else SimpleMaterial(name="sphere")
365
378
  self._group = pyglet.model.MaterialGroup(material=self._material, program=self._program, parent=group)
366
379
 
367
380
  self._vlist = self._create_vertexlist()
@@ -369,27 +382,35 @@ class Sphere(Model):
369
382
  super().__init__([self._vlist], [self._group], self._batch)
370
383
 
371
384
  def _create_vertexlist(self):
372
- radius = self._radius
373
- segments = self._segments
385
+ radius = self._radius / 2
386
+ sectors = self._sectors
387
+ stacks = self._stacks
374
388
 
375
389
  vertices = []
390
+ normals = []
376
391
  indices = []
377
392
 
378
- for i in range(segments):
379
- u = i * pi / segments
380
- for j in range(segments):
381
- v = j * 2 * pi / segments
382
- x = radius * sin(u) * cos(v)
383
- y = radius * sin(u) * sin(v)
384
- z = radius * cos(u)
385
- vertices.extend((x, y, z))
386
-
387
- for i in range(segments):
388
- for j in range(segments):
389
- indices.extend((i * segments + j, (i - 1) * segments + j, i * segments + (j - 1)))
390
- indices.extend((i * segments + j, (i + 1) * segments + j, i * segments + (j + 1)))
391
-
392
- normals = vertices
393
+ sector_step = 2 * pi / sectors
394
+ stack_step = pi / stacks
395
+
396
+ for i in range(stacks + 1):
397
+ stack_angle = pi / 2 - i * stack_step
398
+ for j in range(sectors + 1):
399
+ sector_angle = j * sector_step
400
+ vertices.append(radius * cos(stack_angle) * cos(sector_angle)) # x
401
+ vertices.append(radius * cos(stack_angle) * sin(sector_angle)) # y
402
+ vertices.append(radius * sin(stack_angle)) # z
403
+ normals.append(cos(stack_angle) * cos(sector_angle)) # x
404
+ normals.append(cos(stack_angle) * sin(sector_angle)) # y
405
+ normals.append(sin(stack_angle)) # z
406
+
407
+ # Generate indices
408
+ for i in range(stacks):
409
+ for j in range(sectors):
410
+ first = i * (sectors + 1) + j
411
+ second = first + sectors + 1
412
+ indices.extend([first, second, second + 1])
413
+ indices.extend([first, second + 1, first + 1])
393
414
 
394
415
  return self._program.vertex_list_indexed(len(vertices) // 3, pyglet.gl.GL_TRIANGLES, indices,
395
416
  batch=self._batch, group=self._group,
pyglet/shapes.py CHANGED
@@ -814,7 +814,7 @@ class Arc(ShapeBase):
814
814
  The angle of the arc, in degrees. Defaults to 360.0, which is
815
815
  a full circle.
816
816
  start_angle:
817
- The start angle of the arc, in radians. Defaults to 0.
817
+ The start angle of the arc, in degrees. Defaults to 0.
818
818
  closed:
819
819
  If ``True``, the ends of the arc will be connected with a line.
820
820
  defaults to ``False``.
pyglet/text/document.py CHANGED
@@ -247,7 +247,7 @@ class AbstractDocument(event.EventDispatcher):
247
247
  self._text = ""
248
248
  self._elements: list[InlineElement] = []
249
249
  if text:
250
- self.append_text(text)
250
+ self.insert_text(0, text)
251
251
 
252
252
  @property
253
253
  def text(self) -> str:
@@ -384,24 +384,6 @@ class AbstractDocument(event.EventDispatcher):
384
384
  if element._position >= start: # noqa: SLF001
385
385
  element._position += len_text # noqa: SLF001
386
386
 
387
- def append_text(self, text: str, attributes: dict[str, Any] | None = None) -> None:
388
- """Append text into the end of document.
389
-
390
- Dispatches an :py:meth:`~pyglet.text.document.AbstractDocument.on_insert_text` event.
391
-
392
- Args:
393
- text:
394
- Text to append.
395
- attributes:
396
- Optional dictionary giving named style attributes of the appended text.
397
- """ # noqa: D411, D405, D214, D410
398
- start = len(self._text)
399
- self._append_text(text, attributes)
400
- self.dispatch_event("on_insert_text", start, text)
401
-
402
- def _append_text(self, text: str, attributes: dict[str, Any] | None) -> None:
403
- self._text += text
404
-
405
387
  def delete_text(self, start: int, end: int) -> None:
406
388
  """Delete text from the document.
407
389
 
@@ -645,49 +627,21 @@ class FormattedDocument(AbstractDocument):
645
627
  def get_element_runs(self) -> _ElementIterator:
646
628
  return _ElementIterator(self._elements, len(self._text))
647
629
 
648
- def _insert_text(self, start: int, text: str, attributes: dict[str, Any] | None) -> None:
630
+ def _insert_text(self, start: int, text: str, attributes: dict[str, Any]) -> None:
649
631
  super()._insert_text(start, text, attributes)
650
632
 
651
633
  len_text = len(text)
652
- if attributes is None:
653
- for runs in self._style_runs.values():
654
- runs.insert(start, len_text)
655
-
656
- else:
657
- for name, runs in self._style_runs.items():
658
- if name not in attributes:
659
- runs.insert(start, len_text)
660
-
661
- for attribute, value in attributes.items():
662
- try:
663
- runs = self._style_runs[attribute]
664
- except KeyError:
665
- runs = self._style_runs[attribute] = runlist.RunList(0, None)
666
- runs.append(len(self.text))
667
- runs.set_run(start, start+len_text, value)
668
- else:
669
- runs.insert_run(start, len_text, value)
670
-
671
- def _append_text(self, text: str, attributes: dict[str, Any] | None) -> None:
672
- super()._append_text(text, attributes)
673
-
674
- len_text = len(text)
675
- if attributes is None:
676
- for runs in self._style_runs.values():
677
- runs.append(len_text)
678
-
679
- else:
680
- for name, runs in self._style_runs.items():
681
- if name not in attributes:
682
- runs.append(len_text)
634
+ for runs in self._style_runs.values():
635
+ runs.insert(start, len_text)
683
636
 
637
+ if attributes is not None:
684
638
  for attribute, value in attributes.items():
685
639
  try:
686
640
  runs = self._style_runs[attribute]
687
641
  except KeyError:
688
642
  runs = self._style_runs[attribute] = runlist.RunList(0, None)
689
- runs.append(len(self.text) - len_text)
690
- runs.append_run(len_text, value)
643
+ runs.insert(0, len(self.text))
644
+ runs.set_run(start, start + len_text, value)
691
645
 
692
646
  def _delete_text(self, start: int, end: int) -> None:
693
647
  super()._delete_text(start, end)
@@ -30,6 +30,7 @@ class AttributedTextDecoder(pyglet.text.DocumentDecoder): # noqa: D101
30
30
 
31
31
  def __init__(self) -> None: # noqa: D107
32
32
  self.doc = pyglet.text.document.FormattedDocument()
33
+ self.length = 0
33
34
  self.attributes = {}
34
35
 
35
36
  def decode(self, text: str, location: Location | None = None) -> pyglet.text.document.FormattedDocument: # noqa: ARG002
@@ -77,5 +78,6 @@ class AttributedTextDecoder(pyglet.text.DocumentDecoder): # noqa: D101
77
78
  return self.doc
78
79
 
79
80
  def append(self, text: str) -> None:
80
- self.doc.append_text(text, self.attributes)
81
+ self.doc.insert_text(self.length, text, self.attributes)
82
+ self.length += len(text)
81
83
  self.attributes.clear()
@@ -13,5 +13,5 @@ if TYPE_CHECKING:
13
13
  class PlainTextDecoder(pyglet.text.DocumentDecoder): # noqa: D101
14
14
  def decode(self, text: str, location: Location | None=None) -> UnformattedDocument: # noqa: ARG002
15
15
  document = pyglet.text.document.UnformattedDocument()
16
- document.append_text(text)
16
+ document.insert_text(0, text)
17
17
  return document
@@ -315,7 +315,7 @@ class StructuredTextDecoder(pyglet.text.DocumentDecoder): # noqa: D101
315
315
  break
316
316
 
317
317
  def add_text(self, text: str) -> None:
318
- self.document.append_text(text, self.next_style)
318
+ self.document.insert_text(self.len_text, text, self.next_style)
319
319
  self.next_style.clear()
320
320
  self.len_text += len(text)
321
321