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/quartz.py
CHANGED
|
@@ -3,17 +3,114 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
import math
|
|
5
5
|
import warnings
|
|
6
|
-
from ctypes import byref,
|
|
6
|
+
from ctypes import byref, c_int32, c_void_p, string_at, cast, c_char_p, c_float
|
|
7
7
|
from typing import BinaryIO
|
|
8
8
|
|
|
9
9
|
import pyglet.image
|
|
10
10
|
from pyglet.font import base
|
|
11
|
-
from pyglet.
|
|
11
|
+
from pyglet.font.base import Glyph, GlyphPosition
|
|
12
|
+
from pyglet.libs.darwin import CGFloat, cocoapy, kCTFontURLAttribute, cfnumber_to_number, \
|
|
13
|
+
kCTFontWeightTrait
|
|
14
|
+
from pyglet.font.harfbuzz import harfbuzz_available, get_resource_from_ct_font, \
|
|
15
|
+
get_harfbuzz_shaped_glyphs
|
|
16
|
+
|
|
12
17
|
|
|
13
18
|
cf = cocoapy.cf
|
|
14
19
|
ct = cocoapy.ct
|
|
15
20
|
quartz = cocoapy.quartz
|
|
16
21
|
|
|
22
|
+
UIFontWeightUltraLight = -0.8
|
|
23
|
+
UIFontWeightThin = -0.6
|
|
24
|
+
UIFontWeightLight = -0.4
|
|
25
|
+
UIFontWeightRegular = 0.0
|
|
26
|
+
UIFontWeightMedium = 0.23
|
|
27
|
+
UIFontWeightSemibold = 0.3
|
|
28
|
+
UIFontWeightBold = 0.4
|
|
29
|
+
UIFontWeightHeavy = 0.56
|
|
30
|
+
UIFontWeightBlack = 0.62
|
|
31
|
+
|
|
32
|
+
name_to_weight = {
|
|
33
|
+
True: UIFontWeightBold, # Bold as default for True
|
|
34
|
+
False: UIFontWeightRegular, # Regular for False
|
|
35
|
+
None: UIFontWeightRegular, # Regular if no weight provided
|
|
36
|
+
"thin": UIFontWeightThin,
|
|
37
|
+
"extralight": UIFontWeightUltraLight,
|
|
38
|
+
"ultralight": UIFontWeightUltraLight,
|
|
39
|
+
"light": UIFontWeightLight,
|
|
40
|
+
"semilight": UIFontWeightLight,
|
|
41
|
+
"normal": UIFontWeightRegular,
|
|
42
|
+
"regular": UIFontWeightRegular,
|
|
43
|
+
"medium": UIFontWeightMedium,
|
|
44
|
+
"demibold": UIFontWeightSemibold,
|
|
45
|
+
"semibold": UIFontWeightSemibold,
|
|
46
|
+
"bold": UIFontWeightBold,
|
|
47
|
+
"extrabold": UIFontWeightBold,
|
|
48
|
+
"ultrabold": UIFontWeightBold,
|
|
49
|
+
"black": UIFontWeightBlack,
|
|
50
|
+
"heavy": UIFontWeightHeavy,
|
|
51
|
+
"extrablack": UIFontWeightBlack,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
name_to_stretch = {
|
|
55
|
+
None: 1.0,
|
|
56
|
+
False: 1.0,
|
|
57
|
+
"undefined": 1.0,
|
|
58
|
+
"ultracondensed": -0.4,
|
|
59
|
+
"extracondensed": -0.3,
|
|
60
|
+
"condensed": -0.2,
|
|
61
|
+
"semicondensed": -0.1,
|
|
62
|
+
"normal": 0.0,
|
|
63
|
+
"medium": 0.0,
|
|
64
|
+
"semiexpanded": 0.1,
|
|
65
|
+
"expanded": 0.2,
|
|
66
|
+
"extraexpanded": 0.3,
|
|
67
|
+
"ultraexpanded": 0.4,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if harfbuzz_available():
|
|
72
|
+
"""Build the callbacks and information needed for Harfbuzz to work with CoreText Fonts.
|
|
73
|
+
|
|
74
|
+
Getting the font data is not always reliable, and since no other way exists to
|
|
75
|
+
retrieve the full font bytes from memory, we must construct callbacks for harfbuzz
|
|
76
|
+
to retrieve the tag tables.
|
|
77
|
+
"""
|
|
78
|
+
from pyglet.font.harfbuzz.harfbuzz_lib import hb_lib, hb_destroy_func_t, hb_reference_table_func_t, HB_MEMORY_MODE_READONLY
|
|
79
|
+
|
|
80
|
+
def py_coretext_table_data_destroy(user_data: c_void_p):
|
|
81
|
+
"""Release the table resources once harfbuzz is done."""
|
|
82
|
+
if user_data:
|
|
83
|
+
cf.CFRelease(user_data)
|
|
84
|
+
|
|
85
|
+
py_coretext_table_data_destroy_c = hb_destroy_func_t(py_coretext_table_data_destroy)
|
|
86
|
+
|
|
87
|
+
@hb_reference_table_func_t
|
|
88
|
+
def py_coretext_table_callback(face: c_void_p, tag: int, user_data: c_void_p):
|
|
89
|
+
"""This callback is invoked by HarfBuzz for each table it needs.
|
|
90
|
+
|
|
91
|
+
user_data is a pointer to the CGFont.
|
|
92
|
+
"""
|
|
93
|
+
# Use Quartz to get the table data for the given tag.
|
|
94
|
+
table_data = quartz.CGFontCopyTableForTag(user_data, tag)
|
|
95
|
+
if table_data is None:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
# Get the length and pointer to the raw data.
|
|
99
|
+
length = cf.CFDataGetLength(table_data)
|
|
100
|
+
data_ptr = cf.CFDataGetBytePtr(table_data)
|
|
101
|
+
if not data_ptr:
|
|
102
|
+
# Release the table_data and return empty blob.
|
|
103
|
+
cf.CFRelease(table_data)
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
# Create a blob that references this table data.
|
|
107
|
+
data_ptr_char = cast(data_ptr, c_char_p)
|
|
108
|
+
blob = hb_lib.hb_blob_create(data_ptr_char, length, HB_MEMORY_MODE_READONLY,
|
|
109
|
+
table_data, py_coretext_table_data_destroy_c)
|
|
110
|
+
return blob
|
|
111
|
+
|
|
112
|
+
# Null callback, so harfbuzz cannot destroy our CGFont.
|
|
113
|
+
_destroy_callback_null = cast(None, hb_destroy_func_t)
|
|
17
114
|
|
|
18
115
|
class QuartzGlyphRenderer(base.GlyphRenderer):
|
|
19
116
|
font: QuartzFont
|
|
@@ -22,6 +119,81 @@ class QuartzGlyphRenderer(base.GlyphRenderer):
|
|
|
22
119
|
super().__init__(font)
|
|
23
120
|
self.font = font
|
|
24
121
|
|
|
122
|
+
def render_index(self, glyph_index: int):
|
|
123
|
+
ctFont = self.font.ctFont
|
|
124
|
+
|
|
125
|
+
# Create an attributed string using text and font.
|
|
126
|
+
# Determine the glyphs involved for the text (if any)
|
|
127
|
+
# Get a bounding rectangle for glyphs in string.
|
|
128
|
+
count = 1
|
|
129
|
+
glyphs = (cocoapy.CGGlyph * count)(glyph_index)
|
|
130
|
+
|
|
131
|
+
rect = ct.CTFontGetBoundingRectsForGlyphs(ctFont, 0, glyphs, None, count)
|
|
132
|
+
|
|
133
|
+
# Get advance for all glyphs in string.
|
|
134
|
+
advance = ct.CTFontGetAdvancesForGlyphs(ctFont, 0, glyphs, None, count)
|
|
135
|
+
|
|
136
|
+
# Set image parameters:
|
|
137
|
+
# We add 2 pixels to the bitmap width and height so that there will be a 1-pixel border
|
|
138
|
+
# around the glyph image when it is placed in the texture atlas. This prevents
|
|
139
|
+
# weird artifacts from showing up around the edges of the rendered glyph textures.
|
|
140
|
+
# We adjust the baseline and lsb of the glyph by 1 pixel accordingly.
|
|
141
|
+
width = max(int(math.ceil(rect.size.width) + 2), 1)
|
|
142
|
+
height = max(int(math.ceil(rect.size.height) + 2), 1)
|
|
143
|
+
baseline = -int(math.floor(rect.origin.y)) + 1
|
|
144
|
+
lsb = int(math.ceil(rect.origin.x)) - 1
|
|
145
|
+
advance = int(round(advance))
|
|
146
|
+
|
|
147
|
+
# Create bitmap context.
|
|
148
|
+
bits_per_components = 8
|
|
149
|
+
bytes_per_row = 4 * width
|
|
150
|
+
colorSpace = c_void_p(quartz.CGColorSpaceCreateDeviceRGB())
|
|
151
|
+
bitmap_context = c_void_p(quartz.CGBitmapContextCreate(
|
|
152
|
+
None,
|
|
153
|
+
width,
|
|
154
|
+
height,
|
|
155
|
+
bits_per_components,
|
|
156
|
+
bytes_per_row,
|
|
157
|
+
colorSpace,
|
|
158
|
+
cocoapy.kCGImageAlphaPremultipliedLast))
|
|
159
|
+
|
|
160
|
+
# Draw text to bitmap context.
|
|
161
|
+
quartz.CGContextSetShouldAntialias(bitmap_context, pyglet.options.text_antialiasing)
|
|
162
|
+
quartz.CGContextSetTextPosition(bitmap_context, -lsb, baseline)
|
|
163
|
+
quartz.CGContextSetRGBFillColor(bitmap_context, 1, 1, 1, 1)
|
|
164
|
+
quartz.CGContextSetFont(bitmap_context, ctFont)
|
|
165
|
+
quartz.CGContextSetFontSize(bitmap_context, self.font.pixel_size)
|
|
166
|
+
quartz.CGContextTranslateCTM(bitmap_context, 0, height) # Move origin to top-left
|
|
167
|
+
quartz.CGContextScaleCTM(bitmap_context, 1, -1) # Flip vertically
|
|
168
|
+
|
|
169
|
+
positions = (cocoapy.CGPoint * 1)(*[cocoapy.CGPoint(0, 0)])
|
|
170
|
+
quartz.CTFontDrawGlyphs(ctFont, glyphs, positions, 1, bitmap_context)
|
|
171
|
+
|
|
172
|
+
# Create an image to get the data out.
|
|
173
|
+
image_ref = c_void_p(quartz.CGBitmapContextCreateImage(bitmap_context))
|
|
174
|
+
|
|
175
|
+
bytes_per_row = quartz.CGImageGetBytesPerRow(image_ref)
|
|
176
|
+
data_provider = c_void_p(quartz.CGImageGetDataProvider(image_ref))
|
|
177
|
+
image_data = c_void_p(quartz.CGDataProviderCopyData(data_provider))
|
|
178
|
+
buffer_size = cf.CFDataGetLength(image_data)
|
|
179
|
+
buffer_ptr = cf.CFDataGetBytePtr(image_data)
|
|
180
|
+
if buffer_ptr:
|
|
181
|
+
buffer = string_at(buffer_ptr, buffer_size)
|
|
182
|
+
|
|
183
|
+
quartz.CGImageRelease(image_ref)
|
|
184
|
+
quartz.CGDataProviderRelease(image_data)
|
|
185
|
+
cf.CFRelease(bitmap_context)
|
|
186
|
+
cf.CFRelease(colorSpace)
|
|
187
|
+
|
|
188
|
+
glyph_image = pyglet.image.ImageData(width, height, "RGBA", buffer, bytes_per_row)
|
|
189
|
+
|
|
190
|
+
glyph = self.font.create_glyph(glyph_image)
|
|
191
|
+
glyph.set_bearings(baseline, lsb, advance)
|
|
192
|
+
|
|
193
|
+
return glyph
|
|
194
|
+
|
|
195
|
+
raise Exception("CG Image buffer could not be read.")
|
|
196
|
+
|
|
25
197
|
def render(self, text: str) -> base.Glyph:
|
|
26
198
|
# Using CTLineDraw seems to be the only way to make sure that the text
|
|
27
199
|
# is drawn with the specified font when that font is a graphics font loaded from
|
|
@@ -33,14 +205,18 @@ class QuartzGlyphRenderer(base.GlyphRenderer):
|
|
|
33
205
|
|
|
34
206
|
# Create an attributed string using text and font.
|
|
35
207
|
attributes = c_void_p(
|
|
36
|
-
cf.CFDictionaryCreateMutable(None,
|
|
208
|
+
cf.CFDictionaryCreateMutable(None, 2, cf.kCFTypeDictionaryKeyCallBacks, cf.kCFTypeDictionaryValueCallBacks)
|
|
209
|
+
)
|
|
37
210
|
cf.CFDictionaryAddValue(attributes, cocoapy.kCTFontAttributeName, ctFont)
|
|
38
|
-
|
|
211
|
+
cf.CFDictionaryAddValue(attributes, cocoapy.kCTForegroundColorFromContextAttributeName , cocoapy.kCFBooleanTrue)
|
|
212
|
+
cf_str = cocoapy.CFSTR(text)
|
|
213
|
+
string = c_void_p(cf.CFAttributedStringCreate(None, cf_str, attributes))
|
|
39
214
|
|
|
40
215
|
# Create a CTLine object to render the string.
|
|
41
216
|
line = c_void_p(ct.CTLineCreateWithAttributedString(string))
|
|
42
217
|
cf.CFRelease(string)
|
|
43
218
|
cf.CFRelease(attributes)
|
|
219
|
+
cf.CFRelease(cf_str)
|
|
44
220
|
|
|
45
221
|
# Determine the glyphs involved for the text (if any)
|
|
46
222
|
count = len(text)
|
|
@@ -77,55 +253,170 @@ class QuartzGlyphRenderer(base.GlyphRenderer):
|
|
|
77
253
|
advance = int(round(advance))
|
|
78
254
|
|
|
79
255
|
# Create bitmap context.
|
|
80
|
-
|
|
81
|
-
|
|
256
|
+
bits_per_components = 8
|
|
257
|
+
bytes_per_row = 4 * width
|
|
82
258
|
colorSpace = c_void_p(quartz.CGColorSpaceCreateDeviceRGB())
|
|
83
|
-
|
|
259
|
+
bitmap_context = c_void_p(quartz.CGBitmapContextCreate(
|
|
84
260
|
None,
|
|
85
261
|
width,
|
|
86
262
|
height,
|
|
87
|
-
|
|
88
|
-
|
|
263
|
+
bits_per_components,
|
|
264
|
+
bytes_per_row,
|
|
89
265
|
colorSpace,
|
|
90
266
|
cocoapy.kCGImageAlphaPremultipliedLast))
|
|
91
267
|
|
|
92
268
|
# Draw text to bitmap context.
|
|
93
|
-
quartz.CGContextSetShouldAntialias(
|
|
94
|
-
quartz.CGContextSetTextPosition(
|
|
95
|
-
|
|
96
|
-
|
|
269
|
+
quartz.CGContextSetShouldAntialias(bitmap_context, pyglet.options.text_antialiasing)
|
|
270
|
+
quartz.CGContextSetTextPosition(bitmap_context, -lsb, baseline)
|
|
271
|
+
quartz.CGContextSetRGBFillColor(bitmap_context, 1, 1, 1, 1) # Render white for multiplying.
|
|
272
|
+
quartz.CGContextTranslateCTM(bitmap_context, 0, height) # Move origin to top-left
|
|
273
|
+
quartz.CGContextScaleCTM(bitmap_context, 1, -1) # Flip vertically
|
|
97
274
|
|
|
275
|
+
ct.CTLineDraw(line, bitmap_context)
|
|
276
|
+
cf.CFRelease(line)
|
|
98
277
|
# Create an image to get the data out.
|
|
99
|
-
|
|
278
|
+
image_ref = c_void_p(quartz.CGBitmapContextCreateImage(bitmap_context))
|
|
100
279
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
280
|
+
bytes_per_row = quartz.CGImageGetBytesPerRow(image_ref)
|
|
281
|
+
data_provider = c_void_p(quartz.CGImageGetDataProvider(image_ref))
|
|
282
|
+
image_data = c_void_p(quartz.CGDataProviderCopyData(data_provider))
|
|
283
|
+
buffer_size = cf.CFDataGetLength(image_data)
|
|
284
|
+
buffer_ptr = cf.CFDataGetBytePtr(image_data)
|
|
285
|
+
if buffer_ptr:
|
|
286
|
+
buffer = string_at(buffer_ptr, buffer_size)
|
|
108
287
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
288
|
+
quartz.CGImageRelease(image_ref)
|
|
289
|
+
quartz.CGDataProviderRelease(image_data)
|
|
290
|
+
cf.CFRelease(bitmap_context)
|
|
291
|
+
cf.CFRelease(colorSpace)
|
|
113
292
|
|
|
114
|
-
|
|
293
|
+
glyph_image = pyglet.image.ImageData(width, height, "RGBA", buffer, bytes_per_row)
|
|
115
294
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
t = list(glyph.tex_coords)
|
|
119
|
-
glyph.tex_coords = t[9:12] + t[6:9] + t[3:6] + t[:3]
|
|
295
|
+
glyph = self.font.create_glyph(glyph_image)
|
|
296
|
+
glyph.set_bearings(baseline, lsb, advance)
|
|
120
297
|
|
|
121
|
-
|
|
298
|
+
return glyph
|
|
299
|
+
|
|
300
|
+
raise Exception("CG Image buffer could not be read.")
|
|
122
301
|
|
|
123
302
|
|
|
124
303
|
class QuartzFont(base.Font):
|
|
125
304
|
glyph_renderer_class: type[base.GlyphRenderer] = QuartzGlyphRenderer
|
|
126
|
-
_loaded_CGFont_table: dict[str, dict[int, c_void_p]] = {}
|
|
305
|
+
_loaded_CGFont_table: dict[str, dict[int, tuple[c_void_p, bytes]]] = {}
|
|
306
|
+
|
|
307
|
+
def __init__(self, name: str, size: float, weight: str = "normal", italic: bool = False, stretch: bool = False,
|
|
308
|
+
dpi: int | None = None) -> None:
|
|
309
|
+
|
|
310
|
+
super().__init__()
|
|
311
|
+
|
|
312
|
+
name = name or "Helvetica"
|
|
313
|
+
|
|
314
|
+
self.dpi = dpi or 96
|
|
315
|
+
self.size = size
|
|
316
|
+
self.pixel_size = size * dpi / 72.0
|
|
317
|
+
self.italic = italic
|
|
318
|
+
self.stretch = stretch
|
|
319
|
+
self.weight = weight
|
|
320
|
+
|
|
321
|
+
if isinstance(weight, str):
|
|
322
|
+
self.weight_value = name_to_weight[weight]
|
|
323
|
+
elif weight is True:
|
|
324
|
+
self.weight_value = name_to_weight["bold"]
|
|
325
|
+
else:
|
|
326
|
+
self.weight_value = None
|
|
327
|
+
|
|
328
|
+
self.italic = italic
|
|
329
|
+
|
|
330
|
+
# Construct traits value.
|
|
331
|
+
traits = 0
|
|
332
|
+
if italic:
|
|
333
|
+
traits |= cocoapy.kCTFontItalicTrait
|
|
334
|
+
|
|
335
|
+
if isinstance(stretch, str):
|
|
336
|
+
self.stretch_value = name_to_stretch[stretch]
|
|
337
|
+
else:
|
|
338
|
+
self.stretch_value = None
|
|
339
|
+
|
|
340
|
+
name = str(name)
|
|
341
|
+
self.traits = traits
|
|
342
|
+
# First see if we can find an appropriate font from our table of loaded fonts.
|
|
343
|
+
result = self._lookup_font_with_family_and_traits(name, traits)
|
|
344
|
+
if result:
|
|
345
|
+
cgFont = result[0]
|
|
346
|
+
# Use cgFont from table to create a CTFont object with the specified size.
|
|
347
|
+
self.ctFont = c_void_p(ct.CTFontCreateWithGraphicsFont(cgFont, self.pixel_size, None, None))
|
|
348
|
+
else:
|
|
349
|
+
# Create a font descriptor for given name and traits and use it to create font.
|
|
350
|
+
descriptor = self._create_font_descriptor(name, traits, self.weight_value, self.stretch_value)
|
|
351
|
+
self.ctFont = c_void_p(ct.CTFontCreateWithFontDescriptor(descriptor, self.pixel_size, None))
|
|
352
|
+
cf.CFRelease(descriptor)
|
|
353
|
+
assert self.ctFont, "Couldn't load font: " + name
|
|
354
|
+
|
|
355
|
+
string = c_void_p(ct.CTFontCopyFamilyName(self.ctFont))
|
|
356
|
+
self._family_name = str(cocoapy.cfstring_to_string(string))
|
|
357
|
+
cf.CFRelease(string)
|
|
358
|
+
|
|
359
|
+
self.ascent = int(math.ceil(ct.CTFontGetAscent(self.ctFont)))
|
|
360
|
+
self.descent = -int(math.ceil(ct.CTFontGetDescent(self.ctFont)))
|
|
361
|
+
|
|
362
|
+
if pyglet.options.text_shaping == 'harfbuzz' and harfbuzz_available():
|
|
363
|
+
self._cg_font = None
|
|
364
|
+
self.hb_resource = get_resource_from_ct_font(self)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _get_hb_face(self):
|
|
368
|
+
assert self._cg_font is None
|
|
369
|
+
|
|
370
|
+
# Create a CGFont from the CTFont for the face.
|
|
371
|
+
self._cg_font = quartz.CTFontCopyGraphicsFont(self.ctFont, None)
|
|
372
|
+
if self._cg_font is None:
|
|
373
|
+
raise ValueError("Could not get CGFont from CTFont")
|
|
374
|
+
|
|
375
|
+
# Create the HarfBuzz face using our table callback.
|
|
376
|
+
return hb_lib.hb_face_create_for_tables(py_coretext_table_callback, self._cg_font, _destroy_callback_null)
|
|
377
|
+
|
|
378
|
+
def get_font_data(self) -> bytes:
|
|
379
|
+
"""Get the font file in bytes if possible.
|
|
380
|
+
|
|
381
|
+
Unfortunately CoreText doesn't have a good way to retrieve directly from a font object. Attempt to get the
|
|
382
|
+
filename from the system. If the filename is unknown, it most likely was loaded from memory. In which case,
|
|
383
|
+
the data was added and cached at some point with `add_font_data`.
|
|
384
|
+
"""
|
|
385
|
+
filename = self.filename
|
|
386
|
+
if filename == "Unknown":
|
|
387
|
+
result = self._lookup_font_with_family_and_traits(self.name, self.traits)
|
|
388
|
+
if result:
|
|
389
|
+
_, data = result
|
|
390
|
+
else:
|
|
391
|
+
raise Exception("Couldn't load font data by name and traits. Report as a bug with the font file.")
|
|
392
|
+
else:
|
|
393
|
+
with open(filename, "rb") as f:
|
|
394
|
+
data = f.read()
|
|
395
|
+
|
|
396
|
+
return data
|
|
397
|
+
|
|
398
|
+
@property
|
|
399
|
+
def filename(self) -> str:
|
|
400
|
+
descriptor = self._create_font_descriptor(self.name, self.traits, self.weight_value, self.stretch_value)
|
|
401
|
+
ref = c_void_p(ct.CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute))
|
|
402
|
+
if ref:
|
|
403
|
+
url = cocoapy.ObjCInstance(ref) # NSURL
|
|
404
|
+
filepath = url.fileSystemRepresentation().decode()
|
|
405
|
+
cf.CFRelease(ref)
|
|
406
|
+
cf.CFRelease(descriptor)
|
|
407
|
+
return filepath
|
|
408
|
+
|
|
409
|
+
cf.CFRelease(descriptor)
|
|
410
|
+
return "Unknown"
|
|
127
411
|
|
|
128
|
-
|
|
412
|
+
@property
|
|
413
|
+
def name(self) -> str:
|
|
414
|
+
return self._family_name
|
|
415
|
+
|
|
416
|
+
def __del__(self) -> None:
|
|
417
|
+
cf.CFRelease(self.ctFont)
|
|
418
|
+
|
|
419
|
+
def _lookup_font_with_family_and_traits(self, family: str, traits: int) -> tuple[c_void_p, bytes] | None:
|
|
129
420
|
# This method searches the _loaded_CGFont_table to find a loaded
|
|
130
421
|
# font of the given family with the desired traits. If it can't find
|
|
131
422
|
# anything with the exact traits, it tries to fall back to whatever
|
|
@@ -152,7 +443,9 @@ class QuartzFont(base.Font):
|
|
|
152
443
|
# Otherwise return whatever we have.
|
|
153
444
|
return list(fonts.values())[0]
|
|
154
445
|
|
|
155
|
-
def _create_font_descriptor(self, family_name: str, traits: int
|
|
446
|
+
def _create_font_descriptor(self, family_name: str, traits: int,
|
|
447
|
+
weight: float | None = None,
|
|
448
|
+
stretch: float | None = None) -> c_void_p:
|
|
156
449
|
# Create an attribute dictionary.
|
|
157
450
|
attributes = c_void_p(
|
|
158
451
|
cf.CFDictionaryCreateMutable(None, 0, cf.kCFTypeDictionaryKeyCallBacks, cf.kCFTypeDictionaryValueCallBacks))
|
|
@@ -160,6 +453,7 @@ class QuartzFont(base.Font):
|
|
|
160
453
|
cfname = cocoapy.CFSTR(family_name)
|
|
161
454
|
cf.CFDictionaryAddValue(attributes, cocoapy.kCTFontFamilyNameAttribute, cfname)
|
|
162
455
|
cf.CFRelease(cfname)
|
|
456
|
+
|
|
163
457
|
# Construct a CFNumber to represent the traits.
|
|
164
458
|
itraits = c_int32(traits)
|
|
165
459
|
symTraits = c_void_p(cf.CFNumberCreate(None, cocoapy.kCFNumberSInt32Type, byref(itraits)))
|
|
@@ -168,99 +462,51 @@ class QuartzFont(base.Font):
|
|
|
168
462
|
traitsDict = c_void_p(cf.CFDictionaryCreateMutable(None, 0, cf.kCFTypeDictionaryKeyCallBacks,
|
|
169
463
|
cf.kCFTypeDictionaryValueCallBacks))
|
|
170
464
|
if traitsDict:
|
|
465
|
+
if weight is not None:
|
|
466
|
+
weight_value = c_float(weight)
|
|
467
|
+
cfWeight = c_void_p(cf.CFNumberCreate(None, cocoapy.kCFNumberFloatType, byref(weight_value)))
|
|
468
|
+
if cfWeight:
|
|
469
|
+
cf.CFDictionaryAddValue(traitsDict, cocoapy.kCTFontWeightTrait, cfWeight)
|
|
470
|
+
cf.CFRelease(cfWeight)
|
|
471
|
+
|
|
472
|
+
if stretch is not None:
|
|
473
|
+
stretch_value = c_float(stretch)
|
|
474
|
+
cfWidth = c_void_p(cf.CFNumberCreate(None, cocoapy.kCFNumberFloatType, byref(stretch_value)))
|
|
475
|
+
if cfWidth:
|
|
476
|
+
cf.CFDictionaryAddValue(traitsDict, cocoapy.kCTFontWidthTrait, cfWidth)
|
|
477
|
+
cf.CFRelease(cfWidth)
|
|
478
|
+
|
|
171
479
|
# Add CFNumber traits to traits dictionary.
|
|
172
480
|
cf.CFDictionaryAddValue(traitsDict, cocoapy.kCTFontSymbolicTrait, symTraits)
|
|
173
481
|
# Add traits dictionary to attributes.
|
|
174
482
|
cf.CFDictionaryAddValue(attributes, cocoapy.kCTFontTraitsAttribute, traitsDict)
|
|
175
483
|
cf.CFRelease(traitsDict)
|
|
484
|
+
|
|
176
485
|
cf.CFRelease(symTraits)
|
|
177
486
|
# Create font descriptor with attributes.
|
|
178
487
|
descriptor = c_void_p(ct.CTFontDescriptorCreateWithAttributes(attributes))
|
|
179
488
|
cf.CFRelease(attributes)
|
|
180
489
|
return descriptor
|
|
181
490
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if stretch:
|
|
186
|
-
warnings.warn("The current font render does not support stretching.") # noqa: B028
|
|
187
|
-
|
|
188
|
-
super().__init__()
|
|
189
|
-
|
|
190
|
-
name = name or "Helvetica"
|
|
191
|
-
|
|
192
|
-
# I don't know what is the right thing to do here.
|
|
193
|
-
dpi = dpi or 96
|
|
194
|
-
size = size * dpi / 72.0
|
|
195
|
-
|
|
196
|
-
# Construct traits value.
|
|
197
|
-
traits = 0
|
|
198
|
-
|
|
199
|
-
# TODO: Use kCTFontWeightTrait instead, and
|
|
200
|
-
# translate to the correct weight values.
|
|
201
|
-
if isinstance(weight, str) and "bold" in weight:
|
|
202
|
-
traits |= cocoapy.kCTFontBoldTrait
|
|
203
|
-
elif weight is True:
|
|
204
|
-
traits |= cocoapy.kCTFontBoldTrait
|
|
205
|
-
|
|
206
|
-
if italic:
|
|
207
|
-
traits |= cocoapy.kCTFontItalicTrait
|
|
208
|
-
|
|
491
|
+
@classmethod
|
|
492
|
+
def have_font(cls: type[QuartzFont], name: str) -> bool:
|
|
209
493
|
name = str(name)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
cgFont = self._lookup_font_with_family_and_traits(name, traits)
|
|
213
|
-
if cgFont:
|
|
214
|
-
# Use cgFont from table to create a CTFont object with the specified size.
|
|
215
|
-
self.ctFont = c_void_p(ct.CTFontCreateWithGraphicsFont(cgFont, size, None, None))
|
|
216
|
-
else:
|
|
217
|
-
# Create a font descriptor for given name and traits and use it to create font.
|
|
218
|
-
descriptor = self._create_font_descriptor(name, traits)
|
|
219
|
-
self.ctFont = c_void_p(ct.CTFontCreateWithFontDescriptor(descriptor, size, None))
|
|
220
|
-
cf.CFRelease(descriptor)
|
|
221
|
-
assert self.ctFont, "Couldn't load font: " + name
|
|
494
|
+
if name in cls._loaded_CGFont_table:
|
|
495
|
+
return True
|
|
222
496
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
497
|
+
# Query CoreText to see if the font exists.
|
|
498
|
+
descriptor = ct.CTFontDescriptorCreateWithNameAndSize(cocoapy.CFSTR(name), 0.0)
|
|
499
|
+
matched = ct.CTFontDescriptorCreateMatchingFontDescriptor(descriptor, None)
|
|
226
500
|
|
|
227
|
-
|
|
228
|
-
self.descent = -int(math.ceil(ct.CTFontGetDescent(self.ctFont)))
|
|
501
|
+
exists = True if matched else False
|
|
229
502
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
descriptor = self._create_font_descriptor(self.name, self.traits)
|
|
233
|
-
ref = c_void_p(ct.CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute))
|
|
234
|
-
if ref:
|
|
235
|
-
url = cocoapy.ObjCInstance(ref, cache=False) # NSURL
|
|
236
|
-
filepath = url.fileSystemRepresentation().decode()
|
|
237
|
-
cf.CFRelease(ref)
|
|
238
|
-
return filepath
|
|
239
|
-
|
|
240
|
-
cf.CFRelease(descriptor)
|
|
241
|
-
return "Unknown"
|
|
242
|
-
|
|
243
|
-
@property
|
|
244
|
-
def name(self) -> str:
|
|
245
|
-
return self._family_name
|
|
503
|
+
if descriptor:
|
|
504
|
+
cf.CFRelease(descriptor)
|
|
246
505
|
|
|
247
|
-
|
|
248
|
-
|
|
506
|
+
if matched:
|
|
507
|
+
cf.CFRelease(matched)
|
|
249
508
|
|
|
250
|
-
|
|
251
|
-
def have_font(cls: type[QuartzFont], name: str) -> bool:
|
|
252
|
-
name = str(name)
|
|
253
|
-
if name in cls._loaded_CGFont_table:
|
|
254
|
-
return True
|
|
255
|
-
# Try to create the font to see if it exists.
|
|
256
|
-
# TODO: Find a better way to check.
|
|
257
|
-
cfstring = cocoapy.CFSTR(name)
|
|
258
|
-
cgfont = c_void_p(quartz.CGFontCreateWithFontName(cfstring))
|
|
259
|
-
cf.CFRelease(cfstring)
|
|
260
|
-
if cgfont:
|
|
261
|
-
cf.CFRelease(cgfont)
|
|
262
|
-
return True
|
|
263
|
-
return False
|
|
509
|
+
return exists
|
|
264
510
|
|
|
265
511
|
@classmethod
|
|
266
512
|
def add_font_data(cls: type[QuartzFont], data: BinaryIO) -> None:
|
|
@@ -296,8 +542,82 @@ class QuartzFont(base.Font):
|
|
|
296
542
|
# full name, since its not always clear which one will be looked up.
|
|
297
543
|
if familyName not in cls._loaded_CGFont_table:
|
|
298
544
|
cls._loaded_CGFont_table[familyName] = {}
|
|
299
|
-
cls._loaded_CGFont_table[familyName][traits] = cgFont
|
|
545
|
+
cls._loaded_CGFont_table[familyName][traits] = (cgFont, data)
|
|
300
546
|
|
|
301
547
|
if fullName not in cls._loaded_CGFont_table:
|
|
302
548
|
cls._loaded_CGFont_table[fullName] = {}
|
|
303
|
-
cls._loaded_CGFont_table[fullName][traits] = cgFont
|
|
549
|
+
cls._loaded_CGFont_table[fullName][traits] = (cgFont, data)
|
|
550
|
+
|
|
551
|
+
def get_text_size(self, text: str) -> tuple[int, int]:
|
|
552
|
+
ctFont = self.ctFont
|
|
553
|
+
|
|
554
|
+
attributes = c_void_p(
|
|
555
|
+
cf.CFDictionaryCreateMutable(None, 1, cf.kCFTypeDictionaryKeyCallBacks, cf.kCFTypeDictionaryValueCallBacks)
|
|
556
|
+
)
|
|
557
|
+
cf.CFDictionaryAddValue(attributes, cocoapy.kCTFontAttributeName, ctFont)
|
|
558
|
+
cf_str = cocoapy.CFSTR(text)
|
|
559
|
+
string = c_void_p(cf.CFAttributedStringCreate(None, cf_str, attributes))
|
|
560
|
+
|
|
561
|
+
line = c_void_p(ct.CTLineCreateWithAttributedString(string))
|
|
562
|
+
|
|
563
|
+
ascent, descent = CGFloat(), CGFloat()
|
|
564
|
+
width = ct.CTLineGetTypographicBounds(line, byref(ascent), byref(descent), None)
|
|
565
|
+
height = ascent.value + descent.value
|
|
566
|
+
|
|
567
|
+
cf.CFRelease(string)
|
|
568
|
+
cf.CFRelease(attributes)
|
|
569
|
+
cf.CFRelease(line)
|
|
570
|
+
cf.CFRelease(cf_str)
|
|
571
|
+
return round(width), round(height)
|
|
572
|
+
|
|
573
|
+
def _get_font_weight(self):
|
|
574
|
+
traits = ct.CTFontCopyTraits(self.ctFont)
|
|
575
|
+
font_weight = cfnumber_to_number(c_void_p(cf.CFDictionaryGetValue(traits, kCTFontWeightTrait)))
|
|
576
|
+
cf.CFRelease(traits)
|
|
577
|
+
return font_weight
|
|
578
|
+
|
|
579
|
+
def _get_font_stretch(self):
|
|
580
|
+
traits = ct.CTFontCopyTraits(self.ctFont)
|
|
581
|
+
font_width = cfnumber_to_number(c_void_p(cf.CFDictionaryGetValue(traits, cocoapy.kCTFontWidthTrait)))
|
|
582
|
+
cf.CFRelease(traits)
|
|
583
|
+
return font_width
|
|
584
|
+
|
|
585
|
+
def render_glyph_indices(self, indices: list[int]) -> None:
|
|
586
|
+
# Process any glyphs that have not been rendered.
|
|
587
|
+
self._initialize_renderer()
|
|
588
|
+
|
|
589
|
+
missing = set()
|
|
590
|
+
for glyph_indice in set(indices):
|
|
591
|
+
if glyph_indice not in self.glyphs:
|
|
592
|
+
missing.add(glyph_indice)
|
|
593
|
+
|
|
594
|
+
# Missing glyphs, get their info.
|
|
595
|
+
for glyph_indice in missing:
|
|
596
|
+
self.glyphs[glyph_indice] = self._glyph_renderer.render_index(glyph_indice)
|
|
597
|
+
|
|
598
|
+
def get_glyphs(self, text: str) -> tuple[list[Glyph], list[GlyphPosition]]:
|
|
599
|
+
"""Create and return a list of Glyphs for `text`.
|
|
600
|
+
|
|
601
|
+
If any characters do not have a known glyph representation in this
|
|
602
|
+
font, a substitution will be made.
|
|
603
|
+
|
|
604
|
+
Args:
|
|
605
|
+
text:
|
|
606
|
+
Text to render.
|
|
607
|
+
"""
|
|
608
|
+
self._initialize_renderer()
|
|
609
|
+
|
|
610
|
+
if pyglet.options.text_shaping == "harfbuzz" and harfbuzz_available():
|
|
611
|
+
return get_harfbuzz_shaped_glyphs(self, text)
|
|
612
|
+
|
|
613
|
+
glyphs = [] # glyphs that are committed.
|
|
614
|
+
offsets = []
|
|
615
|
+
for c in base.get_grapheme_clusters(str(text)):
|
|
616
|
+
if c == "\t":
|
|
617
|
+
c = " " # noqa: PLW2901
|
|
618
|
+
if c not in self.glyphs:
|
|
619
|
+
self.glyphs[c] = self._glyph_renderer.render(c)
|
|
620
|
+
glyphs.append(self.glyphs[c])
|
|
621
|
+
offsets.append(base.GlyphPosition(0, 0, 0, 0))
|
|
622
|
+
|
|
623
|
+
return glyphs, offsets
|