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.
- pyglet/__init__.py +21 -9
- pyglet/__init__.pyi +3 -1
- pyglet/app/cocoa.py +6 -3
- pyglet/display/win32.py +14 -15
- pyglet/display/xlib_vidmoderestore.py +1 -1
- pyglet/extlibs/earcut.py +2 -2
- pyglet/font/__init__.py +3 -3
- pyglet/font/base.py +118 -51
- pyglet/font/dwrite/__init__.py +1381 -0
- pyglet/font/dwrite/d2d1_lib.py +637 -0
- pyglet/font/dwrite/d2d1_types_lib.py +60 -0
- pyglet/font/dwrite/dwrite_lib.py +1577 -0
- pyglet/font/fontconfig.py +79 -16
- pyglet/font/freetype.py +252 -77
- pyglet/font/freetype_lib.py +234 -125
- pyglet/font/harfbuzz/__init__.py +275 -0
- pyglet/font/harfbuzz/harfbuzz_lib.py +212 -0
- pyglet/font/quartz.py +432 -112
- pyglet/font/user.py +18 -11
- pyglet/font/win32.py +9 -1
- pyglet/gl/wgl.py +94 -87
- pyglet/gl/wglext_arb.py +472 -218
- pyglet/gl/wglext_nv.py +410 -188
- pyglet/gui/widgets.py +6 -1
- pyglet/image/codecs/bmp.py +3 -5
- pyglet/image/codecs/gdiplus.py +28 -9
- pyglet/image/codecs/wic.py +198 -489
- pyglet/image/codecs/wincodec_lib.py +413 -0
- pyglet/input/base.py +3 -2
- pyglet/input/macos/darwin_hid.py +28 -2
- pyglet/input/win32/directinput.py +2 -1
- pyglet/input/win32/xinput.py +10 -9
- pyglet/lib.py +14 -2
- pyglet/libs/darwin/cocoapy/cocoalibs.py +74 -3
- pyglet/libs/darwin/coreaudio.py +0 -2
- pyglet/libs/win32/__init__.py +4 -2
- pyglet/libs/win32/com.py +65 -12
- pyglet/libs/win32/constants.py +1 -0
- pyglet/libs/win32/dinput.py +1 -9
- pyglet/libs/win32/types.py +72 -8
- pyglet/media/codecs/coreaudio.py +1 -0
- pyglet/media/codecs/wmf.py +93 -72
- pyglet/media/devices/win32.py +5 -4
- pyglet/media/drivers/directsound/lib_dsound.py +4 -4
- pyglet/media/drivers/xaudio2/interface.py +21 -17
- pyglet/media/drivers/xaudio2/lib_xaudio2.py +42 -25
- pyglet/shapes.py +1 -1
- pyglet/text/document.py +7 -53
- pyglet/text/formats/attributed.py +3 -1
- pyglet/text/formats/plaintext.py +1 -1
- pyglet/text/formats/structured.py +1 -1
- pyglet/text/layout/base.py +76 -68
- pyglet/text/layout/incremental.py +38 -8
- pyglet/text/layout/scrolling.py +1 -1
- pyglet/text/runlist.py +2 -114
- pyglet/window/win32/__init__.py +1 -3
- {pyglet-2.1.3.dist-info → pyglet-2.1.4.dist-info}/METADATA +1 -1
- {pyglet-2.1.3.dist-info → pyglet-2.1.4.dist-info}/RECORD +60 -54
- pyglet/font/directwrite.py +0 -2798
- {pyglet-2.1.3.dist-info → pyglet-2.1.4.dist-info}/LICENSE +0 -0
- {pyglet-2.1.3.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
|
-
|
|
5
|
-
from
|
|
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.
|
|
14
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
218
|
+
com.VOIDMETHOD(c_void_p)),
|
|
202
219
|
('OnBufferEnd',
|
|
203
|
-
com.VOIDMETHOD(
|
|
220
|
+
com.VOIDMETHOD(c_void_p)),
|
|
204
221
|
('OnLoopEnd',
|
|
205
|
-
com.VOIDMETHOD(
|
|
222
|
+
com.VOIDMETHOD(c_void_p)),
|
|
206
223
|
('OnVoiceError',
|
|
207
|
-
com.VOIDMETHOD(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
365
|
+
class X3DAUDIO_VECTOR(Structure):
|
|
349
366
|
_fields_ = [
|
|
350
367
|
('x', c_float),
|
|
351
368
|
('y', c_float),
|
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
|
|
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.
|
|
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]
|
|
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
|
-
|
|
653
|
-
|
|
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.
|
|
690
|
-
runs.
|
|
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.
|
|
81
|
+
self.doc.insert_text(self.length, text, self.attributes)
|
|
82
|
+
self.length += len(text)
|
|
81
83
|
self.attributes.clear()
|
pyglet/text/formats/plaintext.py
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
|
pyglet/text/layout/base.py
CHANGED
|
@@ -33,6 +33,7 @@ from pyglet.gl import (
|
|
|
33
33
|
)
|
|
34
34
|
from pyglet.graphics import Group
|
|
35
35
|
from pyglet.text import runlist
|
|
36
|
+
from pyglet.font.base import GlyphPosition
|
|
36
37
|
|
|
37
38
|
if TYPE_CHECKING:
|
|
38
39
|
from pyglet.customtypes import AnchorX, AnchorY, ContentVAlign, HorizontalAlign
|
|
@@ -102,7 +103,7 @@ layout_fragment_source = """#version 330 core
|
|
|
102
103
|
|
|
103
104
|
void main()
|
|
104
105
|
{
|
|
105
|
-
final_colors =
|
|
106
|
+
final_colors = texture(text, texture_coords) * text_colors;
|
|
106
107
|
if (scissor == true) {
|
|
107
108
|
if (vert_position.x < scissor_area[0]) discard; // left
|
|
108
109
|
if (vert_position.y < scissor_area[1]) discard; // bottom
|
|
@@ -410,25 +411,26 @@ class _AbstractBox(ABC):
|
|
|
410
411
|
class _GlyphBox(_AbstractBox):
|
|
411
412
|
owner: Texture
|
|
412
413
|
font: Font
|
|
413
|
-
glyphs: list[tuple[int, Glyph]]
|
|
414
|
+
glyphs: list[tuple[int, Glyph, GlyphPosition]]
|
|
414
415
|
advance: int
|
|
415
416
|
vertex_lists: list[_LayoutVertexList]
|
|
416
417
|
|
|
417
|
-
def __init__(self, owner: Texture, font: Font, glyphs: list[tuple[int, Glyph]], advance: int) -> None:
|
|
418
|
+
def __init__(self, owner: Texture, font: Font, glyphs: list[tuple[int, Glyph, GlyphPosition]], advance: int) -> None:
|
|
418
419
|
"""Create a run of glyphs sharing the same texture.
|
|
419
420
|
|
|
420
|
-
:
|
|
421
|
-
|
|
421
|
+
Args:
|
|
422
|
+
owner:
|
|
422
423
|
Texture of all glyphs in this run.
|
|
423
|
-
|
|
424
|
+
font:
|
|
424
425
|
Font of all glyphs in this run.
|
|
425
|
-
|
|
426
|
+
glyphs:
|
|
426
427
|
Pairs of ``(kern, glyph)``, where ``kern`` gives horizontal
|
|
427
428
|
displacement of the glyph in pixels (typically 0).
|
|
428
|
-
|
|
429
|
+
advance:
|
|
429
430
|
Width of glyph run; must correspond to the sum of advances
|
|
430
431
|
and kerns in the glyph list.
|
|
431
|
-
|
|
432
|
+
offsets:
|
|
433
|
+
A list of all position transformations done to each glyph.
|
|
432
434
|
"""
|
|
433
435
|
super().__init__(font.ascent, font.descent, advance, len(glyphs))
|
|
434
436
|
assert owner
|
|
@@ -446,7 +448,7 @@ class _GlyphBox(_AbstractBox):
|
|
|
446
448
|
rotation: float, visible: bool, anchor_x: float, anchor_y: float, context: _LayoutContext) -> None:
|
|
447
449
|
# Creates the initial attributes and vertex lists of the glyphs.
|
|
448
450
|
# line_x/line_y are calculated when lines shift. To prevent having to destroy and recalculate the layout
|
|
449
|
-
# everytime
|
|
451
|
+
# everytime it moves, they are merged into the vertices. This way the translation can be moved directly.
|
|
450
452
|
assert self.glyphs
|
|
451
453
|
assert not self.vertex_lists
|
|
452
454
|
try:
|
|
@@ -463,17 +465,19 @@ class _GlyphBox(_AbstractBox):
|
|
|
463
465
|
for start, end, baseline_ in context.baseline_iter.ranges(i, i + n_glyphs):
|
|
464
466
|
baseline = layout._parse_distance(baseline_) # noqa: SLF001
|
|
465
467
|
assert len(self.glyphs[start - i:end - i]) == end - start
|
|
466
|
-
for kern, glyph in self.glyphs[start - i:end - i]:
|
|
468
|
+
for (kern, glyph, glyph_pos) in self.glyphs[start - i:end - i]:
|
|
467
469
|
x1 += kern
|
|
468
470
|
v0, v1, v2, v3 = glyph.vertices
|
|
469
|
-
v0 += x1
|
|
470
|
-
v2 += x1
|
|
471
|
-
v1 += line_y + baseline
|
|
472
|
-
v3 += line_y + baseline
|
|
471
|
+
v0 += x1 + glyph_pos.x_offset
|
|
472
|
+
v2 += x1 + glyph_pos.x_offset
|
|
473
|
+
v1 += line_y + baseline + glyph_pos.y_offset
|
|
474
|
+
v3 += line_y + baseline + glyph_pos.y_offset
|
|
473
475
|
vertices.extend(map(round, [v0, v1, 0, v2, v1, 0, v2, v3, 0, v0, v3, 0]))
|
|
474
476
|
t = glyph.tex_coords
|
|
475
477
|
tex_coords.extend(t)
|
|
476
|
-
x1 += glyph.advance
|
|
478
|
+
x1 += glyph.advance + glyph_pos.x_advance
|
|
479
|
+
v1 += glyph_pos.y_advance
|
|
480
|
+
v3 += glyph_pos.y_advance
|
|
477
481
|
|
|
478
482
|
# Text color
|
|
479
483
|
colors = []
|
|
@@ -519,8 +523,8 @@ class _GlyphBox(_AbstractBox):
|
|
|
519
523
|
for start, end, decoration in context.decoration_iter.ranges(i, i + n_glyphs):
|
|
520
524
|
bg, underline = decoration
|
|
521
525
|
x2 = x1
|
|
522
|
-
for kern, glyph in self.glyphs[start - i:end - i]:
|
|
523
|
-
x2 += glyph.advance + kern
|
|
526
|
+
for (kern, glyph, glyph_pos) in self.glyphs[start - i:end - i]:
|
|
527
|
+
x2 += glyph.advance + kern + glyph_pos.x_advance
|
|
524
528
|
|
|
525
529
|
if bg is not None:
|
|
526
530
|
if len(bg) != 4:
|
|
@@ -619,19 +623,19 @@ class _GlyphBox(_AbstractBox):
|
|
|
619
623
|
|
|
620
624
|
def get_point_in_box(self, position: int) -> int:
|
|
621
625
|
x = 0
|
|
622
|
-
for (kern, glyph) in self.glyphs:
|
|
626
|
+
for (kern, glyph, offset) in self.glyphs:
|
|
623
627
|
if position == 0:
|
|
624
628
|
break
|
|
625
629
|
position -= 1
|
|
626
|
-
x += glyph.advance + kern
|
|
630
|
+
x += glyph.advance + kern + offset.x_advance
|
|
627
631
|
return x
|
|
628
632
|
|
|
629
633
|
def get_position_in_box(self, x: float) -> int:
|
|
630
634
|
position = 0
|
|
631
635
|
last_glyph_x = 0
|
|
632
|
-
for kern, glyph in self.glyphs:
|
|
636
|
+
for (kern, glyph, offset) in self.glyphs:
|
|
633
637
|
last_glyph_x += kern
|
|
634
|
-
if last_glyph_x + glyph.advance // 2 > x:
|
|
638
|
+
if last_glyph_x + glyph.advance + offset.x_advance // 2 > x:
|
|
635
639
|
return position
|
|
636
640
|
position += 1
|
|
637
641
|
last_glyph_x += glyph.advance
|
|
@@ -813,6 +817,9 @@ class TextDecorationGroup(Group):
|
|
|
813
817
|
self.program.stop()
|
|
814
818
|
|
|
815
819
|
|
|
820
|
+
# Just have one object for empty positions in layout. It won't be modified.
|
|
821
|
+
_empty_pos = GlyphPosition(0, 0, 0, 0)
|
|
822
|
+
|
|
816
823
|
class TextLayout:
|
|
817
824
|
"""Lay out and display documents.
|
|
818
825
|
|
|
@@ -978,11 +985,6 @@ class TextLayout:
|
|
|
978
985
|
self.group_cache.clear()
|
|
979
986
|
self._update()
|
|
980
987
|
|
|
981
|
-
@property
|
|
982
|
-
def dpi(self) -> float:
|
|
983
|
-
"""Get DPI used by this layout."""
|
|
984
|
-
return self._dpi
|
|
985
|
-
|
|
986
988
|
@property
|
|
987
989
|
def document(self) -> AbstractDocument:
|
|
988
990
|
"""Document to display.
|
|
@@ -1370,17 +1372,12 @@ class TextLayout:
|
|
|
1370
1372
|
self._update()
|
|
1371
1373
|
|
|
1372
1374
|
@property
|
|
1373
|
-
def dpi(self):
|
|
1374
|
-
"""Get DPI used by this layout.
|
|
1375
|
-
|
|
1376
|
-
Read-only.
|
|
1377
|
-
|
|
1378
|
-
:type: float
|
|
1379
|
-
"""
|
|
1375
|
+
def dpi(self) -> float:
|
|
1376
|
+
"""Get DPI used by this layout."""
|
|
1380
1377
|
return self._dpi
|
|
1381
1378
|
|
|
1382
1379
|
@dpi.setter
|
|
1383
|
-
def dpi(self, value):
|
|
1380
|
+
def dpi(self, value: float) -> None:
|
|
1384
1381
|
self._dpi = value
|
|
1385
1382
|
self._update()
|
|
1386
1383
|
|
|
@@ -1438,9 +1435,10 @@ class TextLayout:
|
|
|
1438
1435
|
|
|
1439
1436
|
def _get_lines(self) -> list[_Line]:
|
|
1440
1437
|
len_text = len(self._document.text)
|
|
1441
|
-
glyphs = self._get_glyphs()
|
|
1442
|
-
owner_runs =
|
|
1443
|
-
|
|
1438
|
+
glyphs, offsets = self._get_glyphs()
|
|
1439
|
+
owner_runs = runlist.RunList(len_text, None)
|
|
1440
|
+
self._get_owner_runs(owner_runs, glyphs, 0, len_text)
|
|
1441
|
+
lines = list(self._flow_glyphs(glyphs, offsets, owner_runs, 0, len_text))
|
|
1444
1442
|
self._content_width = 0
|
|
1445
1443
|
self._line_count = len(lines)
|
|
1446
1444
|
self._flow_lines(lines, 0, self._line_count)
|
|
@@ -1620,8 +1618,9 @@ class TextLayout:
|
|
|
1620
1618
|
else:
|
|
1621
1619
|
self._init_document()
|
|
1622
1620
|
|
|
1623
|
-
def _get_glyphs(self) -> list[_InlineElementBox | Glyph]:
|
|
1621
|
+
def _get_glyphs(self) -> tuple[list[_InlineElementBox | Glyph], list[tuple[int, int]]]:
|
|
1624
1622
|
glyphs = []
|
|
1623
|
+
offsets = []
|
|
1625
1624
|
runs = runlist.ZipRunIterator((
|
|
1626
1625
|
self._document.get_font_runs(dpi=self._dpi),
|
|
1627
1626
|
self._document.get_element_runs()))
|
|
@@ -1629,28 +1628,33 @@ class TextLayout:
|
|
|
1629
1628
|
for start, end, (font, element) in runs.ranges(0, len(text)):
|
|
1630
1629
|
if element:
|
|
1631
1630
|
glyphs.append(_InlineElementBox(element))
|
|
1631
|
+
offsets.append(_empty_pos)
|
|
1632
1632
|
else:
|
|
1633
|
-
|
|
1634
|
-
|
|
1633
|
+
char_glyphs, char_offsets = font.get_glyphs(text[start:end])
|
|
1634
|
+
glyphs.extend(char_glyphs)
|
|
1635
|
+
offsets.extend(char_offsets)
|
|
1636
|
+
|
|
1637
|
+
return glyphs, offsets
|
|
1635
1638
|
|
|
1636
|
-
def _get_owner_runs(self, glyphs: list[_InlineElementBox | Glyph]
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1639
|
+
def _get_owner_runs(self, owner_runs: runlist.RunList, glyphs: list[_InlineElementBox | Glyph], start: int,
|
|
1640
|
+
end: int) -> None:
|
|
1641
|
+
owner = glyphs[start].owner
|
|
1642
|
+
run_start = start
|
|
1640
1643
|
|
|
1641
|
-
|
|
1644
|
+
# TODO avoid glyph slice on non-incremental
|
|
1645
|
+
for i, glyph in enumerate(glyphs[start:end]):
|
|
1642
1646
|
if owner != glyph.owner:
|
|
1643
|
-
owner_runs.
|
|
1647
|
+
owner_runs.set_run(run_start, i + start, owner)
|
|
1644
1648
|
owner = glyph.owner
|
|
1645
|
-
run_start = i
|
|
1646
|
-
owner_runs.
|
|
1647
|
-
return owner_runs
|
|
1649
|
+
run_start = i + start
|
|
1650
|
+
owner_runs.set_run(run_start, end, owner)
|
|
1648
1651
|
|
|
1649
|
-
def _flow_glyphs_wrap(self, glyphs: list[_InlineElementBox | Glyph],
|
|
1652
|
+
def _flow_glyphs_wrap(self, glyphs: list[_InlineElementBox | Glyph],
|
|
1653
|
+
offsets: list[GlyphPosition],
|
|
1654
|
+
owner_runs: runlist.RunList, start: int,
|
|
1650
1655
|
end: int) -> Iterator[_Line]:
|
|
1651
1656
|
# Word-wrap styled text into lines of fixed width.
|
|
1652
1657
|
# Fits glyphs in range start to end into Lines which are then yielded.
|
|
1653
|
-
|
|
1654
1658
|
owner_iterator = owner_runs.get_run_iterator().ranges(start, end)
|
|
1655
1659
|
|
|
1656
1660
|
font_iterator = self._document.get_font_runs(dpi=self._dpi)
|
|
@@ -1721,7 +1725,7 @@ class TextLayout:
|
|
|
1721
1725
|
# Iterate over glyphs in this owner run. `text` is the
|
|
1722
1726
|
# corresponding character data for the glyph, and is used to find
|
|
1723
1727
|
# whitespace and newlines.
|
|
1724
|
-
for (text, glyph) in zip(self.document.text[start:end], glyphs[start:end]):
|
|
1728
|
+
for (text, glyph, offset) in zip(self.document.text[start:end], glyphs[start:end], offsets[start:end]):
|
|
1725
1729
|
if nokern:
|
|
1726
1730
|
kern = 0
|
|
1727
1731
|
nokern = False
|
|
@@ -1747,15 +1751,15 @@ class TextLayout:
|
|
|
1747
1751
|
tab_stop = (((x + line.margin_left) // tab) + 1) * tab
|
|
1748
1752
|
kern = int(tab_stop - x - line.margin_left - glyph.advance)
|
|
1749
1753
|
|
|
1750
|
-
owner_accum.append((kern, glyph))
|
|
1754
|
+
owner_accum.append((kern, glyph, offset))
|
|
1751
1755
|
owner_accum_commit.extend(owner_accum)
|
|
1752
|
-
owner_accum_commit_width += owner_accum_width + glyph.advance + kern
|
|
1753
|
-
eol_ws += glyph.advance + kern
|
|
1756
|
+
owner_accum_commit_width += owner_accum_width + glyph.advance + kern + offset.x_advance
|
|
1757
|
+
eol_ws += glyph.advance + kern + offset.x_advance
|
|
1754
1758
|
|
|
1755
1759
|
owner_accum = []
|
|
1756
1760
|
owner_accum_width = 0
|
|
1757
1761
|
|
|
1758
|
-
x += glyph.advance + kern
|
|
1762
|
+
x += glyph.advance + kern + offset.x_advance
|
|
1759
1763
|
index += 1
|
|
1760
1764
|
|
|
1761
1765
|
# The index at which the next line will begin (the
|
|
@@ -1765,7 +1769,7 @@ class TextLayout:
|
|
|
1765
1769
|
else:
|
|
1766
1770
|
new_paragraph = text in "\n\u2029"
|
|
1767
1771
|
new_line = (text == "\u2028") or new_paragraph
|
|
1768
|
-
if (wrap and self._wrap_lines and x + kern + glyph.advance >= width) or new_line:
|
|
1772
|
+
if (wrap and self._wrap_lines and x + kern + glyph.advance + offset.x_advance >= width) or new_line:
|
|
1769
1773
|
# Either the pending runs have overflowed the allowed
|
|
1770
1774
|
# line width or a newline was encountered. Either
|
|
1771
1775
|
# way, the current line must be flushed.
|
|
@@ -1827,11 +1831,11 @@ class TextLayout:
|
|
|
1827
1831
|
# Remove kern from first glyph of line
|
|
1828
1832
|
if run_accum and hasattr(run_accum, "glyphs") and run_accum.glyphs:
|
|
1829
1833
|
k, g = run_accum[0].glyphs[0]
|
|
1830
|
-
run_accum[0].glyphs[0] = (0, g)
|
|
1834
|
+
run_accum[0].glyphs[0] = (0, g, _empty_pos)
|
|
1831
1835
|
run_accum_width -= k
|
|
1832
1836
|
elif owner_accum:
|
|
1833
|
-
k, g = owner_accum[0]
|
|
1834
|
-
owner_accum[0] = (0, g)
|
|
1837
|
+
k, g, _ = owner_accum[0]
|
|
1838
|
+
owner_accum[0] = (0, g, _empty_pos)
|
|
1835
1839
|
owner_accum_width -= k
|
|
1836
1840
|
else:
|
|
1837
1841
|
nokern = True
|
|
@@ -1843,8 +1847,8 @@ class TextLayout:
|
|
|
1843
1847
|
if isinstance(glyph, _AbstractBox):
|
|
1844
1848
|
# Glyph is already in a box. XXX Ignore kern?
|
|
1845
1849
|
run_accum.append(glyph)
|
|
1846
|
-
run_accum_width += glyph.advance
|
|
1847
|
-
x += glyph.advance
|
|
1850
|
+
run_accum_width += glyph.advance + offset.x_advance
|
|
1851
|
+
x += glyph.advance + offset.x_advance
|
|
1848
1852
|
elif new_paragraph:
|
|
1849
1853
|
# New paragraph started, update wrap style
|
|
1850
1854
|
wrap = wrap_iterator[next_start]
|
|
@@ -1854,9 +1858,9 @@ class TextLayout:
|
|
|
1854
1858
|
elif not new_line:
|
|
1855
1859
|
# If the glyph was any non-whitespace, non-newline
|
|
1856
1860
|
# character, add it to the pending run.
|
|
1857
|
-
owner_accum.append((kern, glyph))
|
|
1858
|
-
owner_accum_width += glyph.advance + kern
|
|
1859
|
-
x += glyph.advance + kern
|
|
1861
|
+
owner_accum.append((kern, glyph, offset))
|
|
1862
|
+
owner_accum_width += glyph.advance + kern + offset.x_advance
|
|
1863
|
+
x += glyph.advance + kern + offset.x_advance
|
|
1860
1864
|
index += 1
|
|
1861
1865
|
eol_ws = 0
|
|
1862
1866
|
|
|
@@ -1882,7 +1886,9 @@ class TextLayout:
|
|
|
1882
1886
|
|
|
1883
1887
|
yield line
|
|
1884
1888
|
|
|
1885
|
-
def _flow_glyphs_single_line(self, glyphs: list[_InlineElementBox | Glyph],
|
|
1889
|
+
def _flow_glyphs_single_line(self, glyphs: list[_InlineElementBox | Glyph],
|
|
1890
|
+
offsets: list[GlyphPosition],
|
|
1891
|
+
owner_runs: runlist.RunList,
|
|
1886
1892
|
start: int, end: int) -> Iterator[_Line]:
|
|
1887
1893
|
owner_iterator = owner_runs.get_run_iterator().ranges(start, end)
|
|
1888
1894
|
font_iterator = self.document.get_font_runs(dpi=self._dpi)
|
|
@@ -1905,9 +1911,11 @@ class TextLayout:
|
|
|
1905
1911
|
owner_glyphs = []
|
|
1906
1912
|
for kern_start, kern_end, kern in kern_iterator.ranges(start, end):
|
|
1907
1913
|
gs = glyphs[kern_start:kern_end]
|
|
1914
|
+
os = offsets[kern_start:kern_end]
|
|
1908
1915
|
width += sum([g.advance for g in gs])
|
|
1909
1916
|
width += kern * (kern_end - kern_start)
|
|
1910
|
-
|
|
1917
|
+
width += sum([o.x_advance for o in os])
|
|
1918
|
+
owner_glyphs.extend(zip([kern] * (kern_end - kern_start), gs, os))
|
|
1911
1919
|
if owner is None:
|
|
1912
1920
|
# Assume glyphs are already boxes.
|
|
1913
1921
|
for kern, glyph in owner_glyphs:
|