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/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
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
138
|
+
self._pitch = self._width
|
|
101
139
|
|
|
102
140
|
def _create_glyph(self) -> base.Glyph:
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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
|
|
444
|
+
return self._weight
|
|
281
445
|
|
|
282
446
|
@property
|
|
283
447
|
def italic(self) -> bool:
|
|
284
|
-
return self.
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
FT_Set_Char_Size(self.ft_face,
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
553
|
+
face_index,
|
|
384
554
|
byref(ft_face))
|
|
385
|
-
|
|
555
|
+
|
|
556
|
+
super().__init__(ft_face)
|
|
557
|
+
|
|
558
|
+
@property
|
|
559
|
+
def face_count(self) -> int:
|
|
560
|
+
return self.ft_face.contents.num_faces
|