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
pyglet/__init__.py
CHANGED
|
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
|
|
15
15
|
from typing import Any, Callable, ItemsView, Sized
|
|
16
16
|
|
|
17
17
|
#: The release version
|
|
18
|
-
version = '2.1.
|
|
18
|
+
version = '2.1.4'
|
|
19
19
|
__version__ = version
|
|
20
20
|
|
|
21
21
|
MIN_PYTHON_VERSION = 3, 8
|
|
@@ -110,6 +110,10 @@ class Options:
|
|
|
110
110
|
debug_trace_depth: int = 1
|
|
111
111
|
debug_trace_flush: bool = True
|
|
112
112
|
|
|
113
|
+
debug_com: bool = False
|
|
114
|
+
"""If ``True``, prints information on COM calls. This can potentially help narrow down issues with certain libraries
|
|
115
|
+
that utilize COM calls. Only applies to the Windows platform."""
|
|
116
|
+
|
|
113
117
|
debug_win32: bool = False
|
|
114
118
|
"""If ``True``, prints error messages related to Windows library calls. Usually get's information from
|
|
115
119
|
``Kernel32.GetLastError``. This information is output to a file called ``debug_win32.log``."""
|
|
@@ -175,6 +179,11 @@ class Options:
|
|
|
175
179
|
.. versionadded:: 2.0
|
|
176
180
|
"""
|
|
177
181
|
|
|
182
|
+
text_antialiasing: bool = True
|
|
183
|
+
"""If ``True``, font renderers will improve text quality by adding antialiasing to the rendered characters. If
|
|
184
|
+
``False``, text quality will appear pixelated.
|
|
185
|
+
"""
|
|
186
|
+
|
|
178
187
|
headless: bool = False
|
|
179
188
|
"""If ``True``, visible Windows are not created and a running desktop environment is not required. This option
|
|
180
189
|
is useful when running pyglet on a headless server, or compute cluster. OpenGL drivers with ``EGL`` support are
|
|
@@ -186,14 +195,17 @@ class Options:
|
|
|
186
195
|
GPU to use. This is only useful on multi-GPU systems.
|
|
187
196
|
"""
|
|
188
197
|
|
|
189
|
-
|
|
190
|
-
"""
|
|
191
|
-
speed up. If your font is simple, monospaced, or you require no advanced OpenType features, this option may be
|
|
192
|
-
useful. You can try enabling this to see if there is any impact on clarity for your font. The advance will be
|
|
193
|
-
determined by the glyph width.
|
|
198
|
+
text_shaping: Literal["platform", "harfbuzz", False] = 'platform'
|
|
199
|
+
"""Determines how text is processed and displayed based on features of the font.
|
|
194
200
|
|
|
195
|
-
|
|
196
|
-
|
|
201
|
+
Valid option names are:
|
|
202
|
+
|
|
203
|
+
* ``False``, Disables the shaping process for text. This may increase performance as it reduces the amount
|
|
204
|
+
of calls during rendering. If your font is simple, monospaced, or you require no advanced OpenType features,
|
|
205
|
+
this option may be useful.
|
|
206
|
+
* ``'platform'``, Uses platform's font system for shaping. Supported by Windows (DirectWrite) and Mac (CoreText).
|
|
207
|
+
* ``'harfbuzz'``, Utilize the harfbuzz library for font shaping. This requires an optional dependency, if not
|
|
208
|
+
found, it will fallback to platform shaping.
|
|
197
209
|
|
|
198
210
|
.. versionadded:: 2.0
|
|
199
211
|
"""
|
|
@@ -270,7 +282,7 @@ class Options:
|
|
|
270
282
|
rescaling and repositioning of content will be necessary, but at the cost of blurry content depending on the extent
|
|
271
283
|
of the stretch. For example, 800x600 at 150% DPI will be 800x600 for `window.get_size()` and 1200x900 for
|
|
272
284
|
`window.get_framebuffer_size()`.
|
|
273
|
-
|
|
285
|
+
|
|
274
286
|
`'platform'`: A DPI aware window is created, however window sizing and framebuffer sizing is not interfered with
|
|
275
287
|
by Pyglet. Final sizes are dictated by the platform the window was created on. It is up to the user to make any
|
|
276
288
|
platform adjustments themselves such as sizing on a platform, mouse coordinate adjustments, or framebuffer size
|
pyglet/__init__.pyi
CHANGED
|
@@ -49,15 +49,17 @@ class Options:
|
|
|
49
49
|
debug_win32: bool
|
|
50
50
|
debug_input: bool
|
|
51
51
|
debug_x11: bool
|
|
52
|
+
debug_com: bool
|
|
52
53
|
shadow_window: bool
|
|
53
54
|
vsync: bool | None
|
|
54
55
|
xsync: bool
|
|
55
56
|
xlib_fullscreen_override_redirect: bool
|
|
56
57
|
search_local_libs: bool
|
|
57
58
|
win32_gdi_font: bool
|
|
59
|
+
text_antialiasing: bool
|
|
58
60
|
headless: bool
|
|
59
61
|
headless_device: int
|
|
60
|
-
|
|
62
|
+
text_shaping: Literal["platform", "harfbuzz", False]
|
|
61
63
|
dw_legacy_naming: bool
|
|
62
64
|
win32_disable_xinput: bool
|
|
63
65
|
com_mta: bool
|
pyglet/app/cocoa.py
CHANGED
|
@@ -113,8 +113,10 @@ class CocoaAlternateEventLoop(EventLoop):
|
|
|
113
113
|
super().__init__()
|
|
114
114
|
self.platform_event_loop = None
|
|
115
115
|
|
|
116
|
-
def run(self, interval=1/60):
|
|
117
|
-
if
|
|
116
|
+
def run(self, interval: float | None = 1/60):
|
|
117
|
+
if interval is None:
|
|
118
|
+
pass # do not schedule redraws
|
|
119
|
+
elif not interval:
|
|
118
120
|
self.clock.schedule(self._redraw_windows)
|
|
119
121
|
else:
|
|
120
122
|
self.clock.schedule_interval(self._redraw_windows, interval)
|
|
@@ -133,7 +135,8 @@ class CocoaAlternateEventLoop(EventLoop):
|
|
|
133
135
|
|
|
134
136
|
self.dispatch_event('on_enter')
|
|
135
137
|
self.is_running = True
|
|
136
|
-
|
|
138
|
+
|
|
139
|
+
self.platform_event_loop.nsapp_start(interval or 0)
|
|
137
140
|
|
|
138
141
|
def exit(self):
|
|
139
142
|
"""Safely exit the event loop at the end of the current iteration.
|
pyglet/display/win32.py
CHANGED
|
@@ -1,35 +1,34 @@
|
|
|
1
|
-
from
|
|
1
|
+
from ctypes import byref, sizeof
|
|
2
2
|
|
|
3
|
-
from pyglet.libs.win32 import
|
|
3
|
+
from pyglet.libs.win32 import _gdi32, _shcore, _user32
|
|
4
4
|
from pyglet.libs.win32.constants import (
|
|
5
5
|
CDS_FULLSCREEN,
|
|
6
6
|
DISP_CHANGE_SUCCESSFUL,
|
|
7
7
|
ENUM_CURRENT_SETTINGS,
|
|
8
|
-
WINDOWS_8_1_OR_GREATER,
|
|
9
|
-
WINDOWS_VISTA_OR_GREATER,
|
|
10
|
-
WINDOWS_10_CREATORS_UPDATE_OR_GREATER,
|
|
11
|
-
USER_DEFAULT_SCREEN_DPI,
|
|
12
8
|
LOGPIXELSX,
|
|
13
9
|
LOGPIXELSY,
|
|
14
|
-
|
|
10
|
+
USER_DEFAULT_SCREEN_DPI,
|
|
11
|
+
WINDOWS_8_1_OR_GREATER,
|
|
12
|
+
WINDOWS_10_CREATORS_UPDATE_OR_GREATER,
|
|
13
|
+
WINDOWS_VISTA_OR_GREATER,
|
|
15
14
|
)
|
|
15
|
+
from pyglet.libs.win32.context_managers import device_context
|
|
16
16
|
from pyglet.libs.win32.types import (
|
|
17
17
|
DEVMODE,
|
|
18
18
|
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
|
|
19
|
-
MONITORINFOEX,
|
|
20
19
|
MONITORENUMPROC,
|
|
20
|
+
MONITORINFOEX,
|
|
21
21
|
PROCESS_PER_MONITOR_DPI_AWARE,
|
|
22
22
|
UINT,
|
|
23
|
-
sizeof,
|
|
24
|
-
byref
|
|
25
23
|
)
|
|
26
|
-
from pyglet.libs.win32.context_managers import device_context
|
|
27
24
|
|
|
25
|
+
from .base import Canvas, Display, Screen, ScreenMode
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
|
|
28
|
+
def set_dpi_awareness() -> None:
|
|
29
|
+
"""Setting DPI varies per Windows version.
|
|
30
|
+
|
|
31
|
+
.. note:: DPI awareness needs to be set before Window, Display, or Screens are initialized.
|
|
33
32
|
"""
|
|
34
33
|
if WINDOWS_10_CREATORS_UPDATE_OR_GREATER:
|
|
35
34
|
_user32.SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
|
|
@@ -165,7 +165,7 @@ def set_initial_mode(mode):
|
|
|
165
165
|
if (display, screen) in _restorable_screens:
|
|
166
166
|
return
|
|
167
167
|
|
|
168
|
-
packet = ModePacket(display, screen, mode.width, mode.height, mode.
|
|
168
|
+
packet = ModePacket(display, screen, mode.width, mode.height, mode.info.dotclock)
|
|
169
169
|
|
|
170
170
|
os.write(_mode_write_pipe, packet.encode())
|
|
171
171
|
_restorable_screens.add((display, screen))
|
pyglet/extlibs/earcut.py
CHANGED
|
@@ -473,8 +473,8 @@ def sortLinked(_list):
|
|
|
473
473
|
# z-order of a point given coords and size of the data bounding box
|
|
474
474
|
def zOrder(x, y, minX, minY, size):
|
|
475
475
|
# coords are transformed into non-negative 15-bit integer range
|
|
476
|
-
x = 32767 * (x - minX)
|
|
477
|
-
y = 32767 * (y - minY)
|
|
476
|
+
x = int(32767 * (x - minX) / size)
|
|
477
|
+
y = int(32767 * (y - minY) / size)
|
|
478
478
|
|
|
479
479
|
x = (x | (x << 8)) & 0x00FF00FF
|
|
480
480
|
x = (x | (x << 4)) & 0x0F0F0F0F
|
pyglet/font/__init__.py
CHANGED
|
@@ -42,7 +42,7 @@ def _get_system_font_class() -> type[Font]:
|
|
|
42
42
|
elif pyglet.compat_platform in ("win32", "cygwin"):
|
|
43
43
|
from pyglet.libs.win32.constants import WINDOWS_7_OR_GREATER
|
|
44
44
|
if WINDOWS_7_OR_GREATER and not pyglet.options["win32_gdi_font"]:
|
|
45
|
-
from pyglet.font.
|
|
45
|
+
from pyglet.font.dwrite import Win32DirectWriteFont
|
|
46
46
|
_font_class = Win32DirectWriteFont
|
|
47
47
|
else:
|
|
48
48
|
from pyglet.font.win32 import GDIPlusFont
|
|
@@ -194,7 +194,7 @@ if not getattr(sys, "is_pyglet_doc_run", False):
|
|
|
194
194
|
_user_fonts = []
|
|
195
195
|
|
|
196
196
|
|
|
197
|
-
def add_file(font: str | BinaryIO) -> None:
|
|
197
|
+
def add_file(font: str | BinaryIO | bytes) -> None:
|
|
198
198
|
"""Add a font to pyglet's search path.
|
|
199
199
|
|
|
200
200
|
In order to load a font that is not installed on the system, you must
|
|
@@ -208,7 +208,7 @@ def add_file(font: str | BinaryIO) -> None:
|
|
|
208
208
|
|
|
209
209
|
Args:
|
|
210
210
|
font:
|
|
211
|
-
Filename
|
|
211
|
+
Filename, file-like object, or bytes to load fonts from.
|
|
212
212
|
|
|
213
213
|
"""
|
|
214
214
|
if isinstance(font, str):
|
pyglet/font/base.py
CHANGED
|
@@ -7,66 +7,66 @@ classes as a documented interface to the concrete classes.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import abc
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
|
|
10
12
|
import unicodedata
|
|
11
13
|
from typing import BinaryIO, ClassVar
|
|
12
14
|
|
|
13
15
|
from pyglet import image
|
|
14
16
|
from pyglet.gl import GL_LINEAR, GL_RGBA, GL_TEXTURE_2D
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
_OTHER_GRAPHEME_EXTEND = {
|
|
19
|
+
chr(x) for x in [0x09be, 0x09d7, 0x0be3, 0x0b57, 0x0bbe, 0x0bd7, 0x0cc2,
|
|
20
|
+
0x0cd5, 0x0cd6, 0x0d3e, 0x0d57, 0x0dcf, 0x0ddf, 0x200c,
|
|
21
|
+
0x200d, 0xff9e, 0xff9f]
|
|
22
|
+
} # skip codepoints above U+10000
|
|
23
|
+
_LOGICAL_ORDER_EXCEPTION = {chr(x) for x in range(0xe40, 0xe45)} | {chr(x) for x in range(0xec0, 0xec4)}
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
_EXTEND_CHARS = {chr(x) for x in [0xe30, 0xe32, 0xe33, 0xe45, 0xeb0, 0xeb2, 0xeb3]}
|
|
22
26
|
|
|
23
27
|
_CR = "\u000d"
|
|
24
28
|
_LF = "\u000a"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
_spacing_mark = lambda c, cc: cc == "Mc" and c not in _other_grapheme_extend
|
|
29
|
+
|
|
30
|
+
_CATEGORY_EXTEND = {"Me", "Mn"}
|
|
31
|
+
_CATEGORY_CONTROL = {"ZI", "Zp", "Cc", "Cf"}
|
|
32
|
+
_CATEGORY_SPACING_MARK = {"Mc"}
|
|
30
33
|
|
|
31
34
|
|
|
32
|
-
def grapheme_break(left: str, right: str) -> bool:
|
|
35
|
+
def grapheme_break(left: str, left_cc: str, right: str, right_cc: str) -> bool:
|
|
36
|
+
"""Determines if there should be a break between characters."""
|
|
33
37
|
# GB1
|
|
34
38
|
if left is None:
|
|
35
39
|
return True
|
|
36
40
|
|
|
37
41
|
# GB2 not required, see end of get_grapheme_clusters
|
|
38
42
|
|
|
39
|
-
# GB3
|
|
43
|
+
# GB3: CR + LF do not break
|
|
40
44
|
if left == _CR and right == _LF:
|
|
41
45
|
return False
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
# GB4
|
|
46
|
-
if _control(left, left_cc):
|
|
47
|
+
# GB4: Break before Control characters
|
|
48
|
+
if left_cc in _CATEGORY_CONTROL and left not in _OTHER_GRAPHEME_EXTEND:
|
|
47
49
|
return True
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# GB5
|
|
52
|
-
if _control(right, right_cc):
|
|
51
|
+
# GB5: Break after Control characters
|
|
52
|
+
if right_cc in _CATEGORY_CONTROL and right not in _OTHER_GRAPHEME_EXTEND:
|
|
53
53
|
return True
|
|
54
54
|
|
|
55
55
|
# GB6, GB7, GB8 not implemented
|
|
56
56
|
|
|
57
|
-
# GB9
|
|
58
|
-
if
|
|
57
|
+
# GB9: Do not break before Extend characters
|
|
58
|
+
if right_cc in _CATEGORY_EXTEND or right in _EXTEND_CHARS:
|
|
59
59
|
return False
|
|
60
60
|
|
|
61
|
-
# GB9a
|
|
62
|
-
if
|
|
61
|
+
# GB9a: Do not break before SpacingMark characters
|
|
62
|
+
if right_cc == "Mc" and right not in _OTHER_GRAPHEME_EXTEND:
|
|
63
63
|
return False
|
|
64
64
|
|
|
65
|
-
# GB9b
|
|
66
|
-
if
|
|
65
|
+
# GB9b: Do not break after Prepend characters
|
|
66
|
+
if left in _LOGICAL_ORDER_EXCEPTION: # noqa: SIM103
|
|
67
67
|
return False
|
|
68
68
|
|
|
69
|
-
#
|
|
69
|
+
# GB999: Default to break
|
|
70
70
|
return True
|
|
71
71
|
|
|
72
72
|
|
|
@@ -83,24 +83,38 @@ def get_grapheme_clusters(text: str) -> list[str]:
|
|
|
83
83
|
List of Unicode grapheme clusters.
|
|
84
84
|
"""
|
|
85
85
|
clusters = []
|
|
86
|
-
|
|
86
|
+
cluster_chars = []
|
|
87
87
|
left = None
|
|
88
|
+
left_cc = None
|
|
89
|
+
|
|
88
90
|
for right in text:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
right_cc = unicodedata.category(right)
|
|
92
|
+
|
|
93
|
+
if cluster_chars and grapheme_break(left, left_cc, right, right_cc):
|
|
94
|
+
clusters.append("".join(cluster_chars))
|
|
95
|
+
cluster_chars.clear()
|
|
96
|
+
|
|
97
|
+
cluster_chars.append(right)
|
|
96
98
|
left = right
|
|
99
|
+
left_cc = right_cc
|
|
100
|
+
|
|
101
|
+
if cluster_chars:
|
|
102
|
+
clusters.append("".join(cluster_chars))
|
|
97
103
|
|
|
98
|
-
# GB2
|
|
99
|
-
if cluster:
|
|
100
|
-
clusters.append(cluster)
|
|
101
104
|
return clusters
|
|
102
105
|
|
|
103
106
|
|
|
107
|
+
#: :meta private:
|
|
108
|
+
@dataclass
|
|
109
|
+
class GlyphPosition:
|
|
110
|
+
"""Positioning offsets for a glyph."""
|
|
111
|
+
__slots__ = ('x_advance', 'x_offset', 'y_advance', 'y_offset')
|
|
112
|
+
x_advance: int # How far the line advances AFTER drawing horizontal.
|
|
113
|
+
y_advance: int # How far the line advances AFTER drawing vertical.
|
|
114
|
+
x_offset: int # How much the current glyph moves on the X-axis when drawn. Does not advance.
|
|
115
|
+
y_offset: int # How much the current glyph moves on the Y-axis when drawn. Does not advance.
|
|
116
|
+
|
|
117
|
+
|
|
104
118
|
class Glyph(image.TextureRegion):
|
|
105
119
|
"""A single glyph located within a larger texture.
|
|
106
120
|
|
|
@@ -116,8 +130,7 @@ class Glyph(image.TextureRegion):
|
|
|
116
130
|
#: :If a glyph is colored by the font renderer, such as an emoji, it may be treated differently by pyglet.
|
|
117
131
|
colored = False
|
|
118
132
|
|
|
119
|
-
def set_bearings(self, baseline: int, left_side_bearing: int, advance: int
|
|
120
|
-
y_offset: int = 0) -> None:
|
|
133
|
+
def set_bearings(self, baseline: int, left_side_bearing: int, advance: int) -> None:
|
|
121
134
|
"""Set metrics for this glyph.
|
|
122
135
|
|
|
123
136
|
Args:
|
|
@@ -127,20 +140,16 @@ class Glyph(image.TextureRegion):
|
|
|
127
140
|
Distance to add to the left edge of the glyph.
|
|
128
141
|
advance:
|
|
129
142
|
Distance to move the horizontal advance to the next glyph, in pixels.
|
|
130
|
-
x_offset:
|
|
131
|
-
Distance to move the glyph horizontally from its default position.
|
|
132
|
-
y_offset:
|
|
133
|
-
Distance to move the glyph vertically from its default position.
|
|
134
143
|
"""
|
|
135
144
|
self.baseline = baseline
|
|
136
145
|
self.lsb = left_side_bearing
|
|
137
146
|
self.advance = advance
|
|
138
147
|
|
|
139
148
|
self.vertices = (
|
|
140
|
-
left_side_bearing
|
|
141
|
-
-baseline
|
|
142
|
-
left_side_bearing + self.width
|
|
143
|
-
-baseline + self.height
|
|
149
|
+
left_side_bearing,
|
|
150
|
+
-baseline,
|
|
151
|
+
left_side_bearing + self.width,
|
|
152
|
+
-baseline + self.height)
|
|
144
153
|
|
|
145
154
|
|
|
146
155
|
class GlyphTexture(image.Texture):
|
|
@@ -191,6 +200,7 @@ class GlyphRenderer(abc.ABC):
|
|
|
191
200
|
Args:
|
|
192
201
|
font: The :py:class:`~pyglet.font.base.Font` object to be rendered.
|
|
193
202
|
"""
|
|
203
|
+
self.font = font
|
|
194
204
|
|
|
195
205
|
@abc.abstractmethod
|
|
196
206
|
def render(self, text: str) -> Glyph:
|
|
@@ -203,6 +213,15 @@ class GlyphRenderer(abc.ABC):
|
|
|
203
213
|
A Glyph with the proper metrics for that specific character.
|
|
204
214
|
"""
|
|
205
215
|
|
|
216
|
+
def create_zero_glyph(self) -> Glyph:
|
|
217
|
+
"""Zero glyph is a 1x1 image that has a -1 advance.
|
|
218
|
+
|
|
219
|
+
This is to fill in for potential substitutions since font system requires 1 glyph per character in a string.
|
|
220
|
+
"""
|
|
221
|
+
image_data = image.ImageData(1, 1, 'RGBA', bytes([0, 0, 0, 0]))
|
|
222
|
+
glyph = self.font.create_glyph(image_data)
|
|
223
|
+
glyph.set_bearings(-self.font.descent, 0, -1)
|
|
224
|
+
return glyph
|
|
206
225
|
|
|
207
226
|
class FontException(Exception): # noqa: N818
|
|
208
227
|
"""Generic exception related to errors from the font module. Typically, from invalid font data."""
|
|
@@ -240,7 +259,7 @@ class Font:
|
|
|
240
259
|
``GL_NEAREST`` to prevent aliasing with pixelated fonts.
|
|
241
260
|
"""
|
|
242
261
|
#: :meta private:
|
|
243
|
-
glyphs: dict[str, Glyph]
|
|
262
|
+
glyphs: dict[str | int, Glyph]
|
|
244
263
|
|
|
245
264
|
texture_width: int = 512
|
|
246
265
|
texture_height: int = 512
|
|
@@ -264,10 +283,47 @@ class Font:
|
|
|
264
283
|
# The default type of texture bins. Should not be overridden by users.
|
|
265
284
|
texture_class: ClassVar[type[GlyphTextureBin]] = GlyphTextureBin
|
|
266
285
|
|
|
286
|
+
# A list of fallback fonts to use when an existing glyph is not found.
|
|
287
|
+
fallbacks: list[Font]
|
|
288
|
+
|
|
289
|
+
_glyph_renderer: GlyphRenderer | None
|
|
290
|
+
_missing_glyph: Glyph | None
|
|
291
|
+
_zero_glyph: Glyph | None
|
|
292
|
+
|
|
293
|
+
# The size of the font in pixels.
|
|
294
|
+
pixel_size: float
|
|
295
|
+
|
|
267
296
|
def __init__(self) -> None:
|
|
268
297
|
"""Initialize a font that can be used with Pyglet."""
|
|
269
298
|
self.texture_bin = None
|
|
299
|
+
self.hb_resource = None
|
|
300
|
+
self._glyph_renderer = None
|
|
301
|
+
|
|
302
|
+
# Represents a missing glyph.
|
|
303
|
+
self._missing_glyph = None
|
|
304
|
+
|
|
305
|
+
# Represents a zero width glyph.
|
|
306
|
+
self._zero_glyph = None
|
|
270
307
|
self.glyphs = {}
|
|
308
|
+
self.fallbacks = []
|
|
309
|
+
|
|
310
|
+
def _initialize_renderer(self) -> None:
|
|
311
|
+
"""Initialize the glyph renderer and cache it on the Font.
|
|
312
|
+
|
|
313
|
+
This way renderers for fonts that have been loaded but not used will not have unnecessary loaders.
|
|
314
|
+
"""
|
|
315
|
+
if not self._glyph_renderer:
|
|
316
|
+
self._glyph_renderer = self.glyph_renderer_class(self)
|
|
317
|
+
self._missing_glyph = self._glyph_renderer.render(" ")
|
|
318
|
+
self._zero_glyph = self._glyph_renderer.create_zero_glyph()
|
|
319
|
+
|
|
320
|
+
def add_fallback(self, font: Font) -> None:
|
|
321
|
+
assert font not in self.fallbacks, "Font is already added."
|
|
322
|
+
self.fallbacks.append(font)
|
|
323
|
+
|
|
324
|
+
def remove_fallback(self, font: Font) -> None:
|
|
325
|
+
assert font not in self.fallbacks, "Font has not been added."
|
|
326
|
+
self.fallbacks.remove(font)
|
|
271
327
|
|
|
272
328
|
@property
|
|
273
329
|
@abc.abstractmethod
|
|
@@ -346,7 +402,7 @@ class Font:
|
|
|
346
402
|
|
|
347
403
|
return atlas_size
|
|
348
404
|
|
|
349
|
-
def get_glyphs(self, text: str) -> list[Glyph]:
|
|
405
|
+
def get_glyphs(self, text: str) -> tuple[list[Glyph], list[GlyphPosition]]:
|
|
350
406
|
"""Create and return a list of Glyphs for `text`.
|
|
351
407
|
|
|
352
408
|
If any characters do not have a known glyph representation in this
|
|
@@ -357,7 +413,9 @@ class Font:
|
|
|
357
413
|
Text to render.
|
|
358
414
|
"""
|
|
359
415
|
glyph_renderer = None
|
|
416
|
+
|
|
360
417
|
glyphs = [] # glyphs that are committed.
|
|
418
|
+
offsets = []
|
|
361
419
|
for c in get_grapheme_clusters(str(text)):
|
|
362
420
|
# Get the glyph for 'c'. Hide tabs (Windows and Linux render
|
|
363
421
|
# boxes)
|
|
@@ -368,7 +426,16 @@ class Font:
|
|
|
368
426
|
glyph_renderer = self.glyph_renderer_class(self)
|
|
369
427
|
self.glyphs[c] = glyph_renderer.render(c)
|
|
370
428
|
glyphs.append(self.glyphs[c])
|
|
371
|
-
|
|
429
|
+
offsets.append(GlyphPosition(0, 0, 0, 0))
|
|
430
|
+
|
|
431
|
+
return glyphs, offsets
|
|
432
|
+
|
|
433
|
+
@abc.abstractmethod
|
|
434
|
+
def get_text_size(self, text: str) -> tuple[int, int]:
|
|
435
|
+
"""Return's an estimated width and height of text using glyph metrics without rendering..
|
|
436
|
+
|
|
437
|
+
This does not take into account any shaping.
|
|
438
|
+
"""
|
|
372
439
|
|
|
373
440
|
def get_glyphs_for_width(self, text: str, width: int) -> list[Glyph]:
|
|
374
441
|
"""Return a list of glyphs for ``text`` that fit within the given width.
|