feat: update pyglet

This commit is contained in:
shenjack 2022-12-29 11:42:26 +08:00
parent c4313c0671
commit d01503cca5
9 changed files with 681 additions and 209 deletions

View File

@ -163,6 +163,7 @@ options = {
'headless': False,
'headless_device': 0,
'win32_disable_shaping': False,
'dw_legacy_naming': False
}
_option_types = {
@ -192,6 +193,7 @@ _option_types = {
'headless': bool,
'headless_device': int,
'win32_disable_shaping': bool,
'dw_legacy_naming': bool
}

View File

@ -1,22 +1,18 @@
import copy
import os
import pathlib
import platform
from ctypes import *
from typing import List, Optional, Tuple
import math
import pyglet
from pyglet.font import base
from pyglet.util import debug_print
from pyglet.image.codecs.wic import IWICBitmap, GUID_WICPixelFormat32bppBGR, WICDecoder, GUID_WICPixelFormat32bppBGRA, \
GUID_WICPixelFormat32bppPBGRA
from pyglet import image
import ctypes
import math
from pyglet.libs.win32 import com
from pyglet.image.codecs.wic import IWICBitmap, WICDecoder, GUID_WICPixelFormat32bppPBGRA
from pyglet.libs.win32 import _kernel32 as kernel32
from pyglet.libs.win32 import _ole32 as ole32
from pyglet.libs.win32.constants import *
from pyglet.libs.win32.types import *
from ctypes import *
import os
import platform
from pyglet.util import debug_print
try:
dwrite = 'dwrite'
@ -33,7 +29,9 @@ except OSError as err:
# Doesn't exist? Should stop import of library.
pass
_debug_font = debug_print('debug_font')
_debug_font = pyglet.options['debug_font']
_debug_print = debug_print('debug_font')
def DWRITE_MAKE_OPENTYPE_TAG(a, b, c, d):
@ -102,6 +100,7 @@ name_to_stretch = {"undefined": DWRITE_FONT_STRETCH_UNDEFINED,
"semiexpanded": DWRITE_FONT_STRETCH_SEMI_EXPANDED,
"expanded": DWRITE_FONT_STRETCH_EXPANDED,
"extraexpanded": DWRITE_FONT_STRETCH_EXTRA_EXPANDED,
"narrow": DWRITE_FONT_STRETCH_CONDENSED,
}
DWRITE_GLYPH_IMAGE_FORMATS = c_int
@ -146,6 +145,34 @@ INT32 = c_int32
UINT32 = c_uint32
UINT64 = c_uint64
DWRITE_INFORMATIONAL_STRING_ID = UINT32
DWRITE_INFORMATIONAL_STRING_NONE = 0
DWRITE_INFORMATIONAL_STRING_COPYRIGHT_NOTICE = 1
DWRITE_INFORMATIONAL_STRING_VERSION_STRINGS = 2
DWRITE_INFORMATIONAL_STRING_TRADEMARK = 3
DWRITE_INFORMATIONAL_STRING_MANUFACTURER = 4
DWRITE_INFORMATIONAL_STRING_DESIGNER = 5
DWRITE_INFORMATIONAL_STRING_DESIGNER_URL = 6
DWRITE_INFORMATIONAL_STRING_DESCRIPTION = 7
DWRITE_INFORMATIONAL_STRING_FONT_VENDOR_URL = 8
DWRITE_INFORMATIONAL_STRING_LICENSE_DESCRIPTION = 9
DWRITE_INFORMATIONAL_STRING_LICENSE_INFO_URL = 10
DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES = 11
DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES = 12
DWRITE_INFORMATIONAL_STRING_TYPOGRAPHIC_FAMILY_NAMES = 13
DWRITE_INFORMATIONAL_STRING_TYPOGRAPHIC_SUBFAMILY_NAMES = 14
DWRITE_INFORMATIONAL_STRING_SAMPLE_TEXT = 15
DWRITE_INFORMATIONAL_STRING_FULL_NAME = 16
DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME = 17
DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_CID_NAME = 18
DWRITE_INFORMATIONAL_STRING_WEIGHT_STRETCH_STYLE_FAMILY_NAME = 19
DWRITE_INFORMATIONAL_STRING_DESIGN_SCRIPT_LANGUAGE_TAG = 20
DWRITE_INFORMATIONAL_STRING_SUPPORTED_SCRIPT_LANGUAGE_TAG = 21
DWRITE_INFORMATIONAL_STRING_PREFERRED_FAMILY_NAMES = 22
DWRITE_INFORMATIONAL_STRING_PREFERRED_SUBFAMILY_NAMES = 23
DWRITE_INFORMATIONAL_STRING_WWS_FAMILY_NAME = 24
class D2D_POINT_2F(Structure):
_fields_ = (
@ -236,12 +263,64 @@ class DWRITE_CLUSTER_METRICS(ctypes.Structure):
)
class IDWriteFontFileStream(com.IUnknown):
_methods_ = [
('ReadFileFragment',
com.STDMETHOD(c_void_p, POINTER(c_void_p), UINT64, UINT64, POINTER(c_void_p))),
('ReleaseFileFragment',
com.STDMETHOD(c_void_p, c_void_p)),
('GetFileSize',
com.STDMETHOD(c_void_p, POINTER(UINT64))),
('GetLastWriteTime',
com.STDMETHOD(c_void_p, POINTER(UINT64))),
]
class IDWriteFontFileLoader_LI(com.IUnknown): # Local implementation use only.
_methods_ = [
('CreateStreamFromKey',
com.STDMETHOD(c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileStream))))
]
class IDWriteFontFileLoader(com.pIUnknown):
_methods_ = [
('CreateStreamFromKey',
com.STDMETHOD(c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileStream))))
]
class IDWriteLocalFontFileLoader(IDWriteFontFileLoader, com.pIUnknown):
_methods_ = [
('GetFilePathLengthFromKey',
com.STDMETHOD(c_void_p, UINT32, POINTER(UINT32))),
('GetFilePathFromKey',
com.STDMETHOD(c_void_p, UINT32, c_wchar_p, UINT32)),
('GetLastWriteTimeFromKey',
com.STDMETHOD())
]
IID_IDWriteLocalFontFileLoader = com.GUID(0xb2d9f3ec, 0xc9fe, 0x4a11, 0xa2, 0xec, 0xd8, 0x62, 0x08, 0xf7, 0xc0, 0xa2)
class IDWriteFontFile(com.pIUnknown):
_methods_ = [
('GetReferenceKey',
com.STDMETHOD(POINTER(c_void_p), POINTER(UINT32))),
('GetLoader',
com.STDMETHOD(POINTER(IDWriteFontFileLoader))),
('Analyze',
com.STDMETHOD()),
]
class IDWriteFontFace(com.pIUnknown):
_methods_ = [
('GetType',
com.STDMETHOD()),
('GetFiles',
com.STDMETHOD()),
com.STDMETHOD(POINTER(UINT32), POINTER(IDWriteFontFile))),
('GetIndex',
com.STDMETHOD()),
('GetSimulations',
@ -582,9 +661,9 @@ class IDWriteFontList(com.pIUnknown):
('GetFontCollection',
com.STDMETHOD()),
('GetFontCount',
com.STDMETHOD()),
com.METHOD(UINT32)),
('GetFont',
com.STDMETHOD()),
com.STDMETHOD(UINT32, c_void_p)), # IDWriteFont, use void because of forward ref.
]
@ -610,33 +689,22 @@ class IDWriteFontFamily1(IDWriteFontFamily, IDWriteFontList, com.pIUnknown):
]
class IDWriteFontFile(com.pIUnknown):
_methods_ = [
('GetReferenceKey',
com.STDMETHOD()),
('GetLoader',
com.STDMETHOD()),
('Analyze',
com.STDMETHOD()),
]
class IDWriteFont(com.pIUnknown):
_methods_ = [
('GetFontFamily',
com.STDMETHOD(POINTER(IDWriteFontFamily))),
('GetWeight',
com.STDMETHOD()),
com.METHOD(DWRITE_FONT_WEIGHT)),
('GetStretch',
com.STDMETHOD()),
com.METHOD(DWRITE_FONT_STRETCH)),
('GetStyle',
com.STDMETHOD()),
com.METHOD(DWRITE_FONT_STYLE)),
('IsSymbolFont',
com.STDMETHOD()),
com.METHOD(BOOL)),
('GetFaceNames',
com.STDMETHOD(POINTER(IDWriteLocalizedStrings))),
('GetInformationalStrings',
com.STDMETHOD()),
com.STDMETHOD(DWRITE_INFORMATIONAL_STRING_ID, POINTER(IDWriteLocalizedStrings), POINTER(BOOL))),
('GetSimulations',
com.STDMETHOD()),
('GetMetrics',
@ -664,7 +732,7 @@ class IDWriteFont1(IDWriteFont, com.pIUnknown):
class IDWriteFontCollection(com.pIUnknown):
_methods_ = [
('GetFontFamilyCount',
com.STDMETHOD()),
com.METHOD(UINT32)),
('GetFontFamily',
com.STDMETHOD(UINT32, POINTER(IDWriteFontFamily))),
('FindFamilyName',
@ -690,6 +758,21 @@ DWRITE_TEXT_ALIGNMENT_CENTER = 3
DWRITE_TEXT_ALIGNMENT_JUSTIFIED = 4
class IDWriteGdiInterop(com.pIUnknown):
_methods_ = [
('CreateFontFromLOGFONT',
com.STDMETHOD(POINTER(LOGFONTW), POINTER(IDWriteFont))),
('ConvertFontToLOGFONT',
com.STDMETHOD()),
('ConvertFontFaceToLOGFONT',
com.STDMETHOD()),
('CreateFontFaceFromHdc',
com.STDMETHOD(HDC, POINTER(IDWriteFontFace))),
('CreateBitmapRenderTarget',
com.STDMETHOD())
]
class IDWriteTextFormat(com.pIUnknown):
_methods_ = [
('SetTextAlignment',
@ -884,19 +967,6 @@ class IDWriteFontCollectionLoader(com.IUnknown):
]
class IDWriteFontFileStream(com.IUnknown):
_methods_ = [
('ReadFileFragment',
com.STDMETHOD(c_void_p, POINTER(c_void_p), UINT64, UINT64, POINTER(c_void_p))),
('ReleaseFileFragment',
com.STDMETHOD(c_void_p, c_void_p)),
('GetFileSize',
com.STDMETHOD(c_void_p, POINTER(UINT64))),
('GetLastWriteTime',
com.STDMETHOD(c_void_p, POINTER(UINT64))),
]
class MyFontFileStream(com.COMObject):
_interfaces_ = [IDWriteFontFileStream]
@ -938,15 +1008,8 @@ class MyFontFileStream(com.COMObject):
return 0x80004001 # E_NOTIMPL
class IDWriteFontFileLoader(com.IUnknown):
_methods_ = [
('CreateStreamFromKey',
com.STDMETHOD(c_void_p, c_void_p, UINT32, POINTER(POINTER(IDWriteFontFileStream))))
]
class LegacyFontFileLoader(com.COMObject):
_interfaces_ = [IDWriteFontFileLoader]
_interfaces_ = [IDWriteFontFileLoader_LI]
def __init__(self):
self._streams = {}
@ -1089,7 +1152,7 @@ class IDWriteFactory(com.pIUnknown):
('CreateFontFileReference',
com.STDMETHOD(c_wchar_p, c_void_p, POINTER(IDWriteFontFile))),
('CreateCustomFontFileReference',
com.STDMETHOD(c_void_p, UINT32, POINTER(IDWriteFontFileLoader), POINTER(IDWriteFontFile))),
com.STDMETHOD(c_void_p, UINT32, POINTER(IDWriteFontFileLoader_LI), POINTER(IDWriteFontFile))),
('CreateFontFace',
com.STDMETHOD()),
('CreateRenderingParams',
@ -1101,14 +1164,14 @@ class IDWriteFactory(com.pIUnknown):
('RegisterFontFileLoader',
com.STDMETHOD(c_void_p)), # Ambigious as newer is a pIUnknown and legacy is IUnknown.
('UnregisterFontFileLoader',
com.STDMETHOD(POINTER(IDWriteFontFileLoader))),
com.STDMETHOD(POINTER(IDWriteFontFileLoader_LI))),
('CreateTextFormat',
com.STDMETHOD(c_wchar_p, IDWriteFontCollection, DWRITE_FONT_WEIGHT, DWRITE_FONT_STYLE, DWRITE_FONT_STRETCH,
FLOAT, c_wchar_p, POINTER(IDWriteTextFormat))),
('CreateTypography',
com.STDMETHOD(POINTER(IDWriteTypography))),
('GetGdiInterop',
com.STDMETHOD()),
com.STDMETHOD(POINTER(IDWriteGdiInterop))),
('CreateTextLayout',
com.STDMETHOD(c_wchar_p, UINT32, IDWriteTextFormat, FLOAT, FLOAT, POINTER(IDWriteTextLayout))),
('CreateGdiCompatibleTextLayout',
@ -1548,6 +1611,13 @@ if not wic_decoder:
raise Exception("Cannot use DirectWrite without a WIC Decoder")
def get_system_locale() -> str:
"""Retrieve the string representing the system locale."""
local_name = create_unicode_buffer(LOCALE_NAME_MAX_LENGTH)
kernel32.GetUserDefaultLocaleName(local_name, LOCALE_NAME_MAX_LENGTH)
return local_name.value
class DirectWriteGlyphRenderer(base.GlyphRenderer):
antialias_mode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT
draw_options = D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT if WINDOWS_8_1_OR_GREATER else D2D1_DRAW_TEXT_OPTIONS_NONE
@ -1782,6 +1852,11 @@ class DirectWriteGlyphRenderer(base.GlyphRenderer):
# Negative LSB: we shift the offset, otherwise the glyph will be cut off.
render_offset_x = glyph_lsb * self.font.font_scale_ratio
# Increase width by arbitrary amount to accommodate size of italic.
# No way to get actual size of italics outside of rendering to larger texture and checking pixels.
if self.font.italic:
render_width += (render_width // 2)
# Create new bitmap.
# TODO: We can probably adjust bitmap/baseline to reduce the whitespace and save a lot of texture space.
# Note: Floating point precision makes this a giant headache, will need to be solved for this approach.
@ -1912,6 +1987,7 @@ class Win32DirectWriteFont(base.Font):
texture_internalformat = pyglet.gl.GL_RGBA
def __init__(self, name, size, bold=False, italic=False, stretch=False, dpi=None, locale=None):
self._filename: Optional[str] = None
self._advance_cache = {} # Stores glyph's by the indice and advance.
super(Win32DirectWriteFont, self).__init__()
@ -1919,9 +1995,6 @@ class Win32DirectWriteFont(base.Font):
if not name:
name = self._default_name
self._font_index, self._collection = self.get_collection(name)
assert self._collection is not None, "Font: {} not found in loaded or system font collection.".format(name)
self._name = name
self.bold = bold
self.size = size
@ -1965,6 +2038,27 @@ class Win32DirectWriteFont(base.Font):
else:
self._stretch = DWRITE_FONT_STRETCH_NORMAL
self._font_index, self._collection = self.get_collection(name)
write_font = None
# If not font found, search all collections for legacy GDI naming.
if pyglet.options["dw_legacy_naming"]:
if self._font_index is None and self._collection is None:
write_font, self._collection = self.find_font_face(name, self._weight, self._style, self._stretch)
assert self._collection is not None, f"Font: '{name}' not found in loaded or system font collection."
if self._font_index is not None:
font_family = IDWriteFontFamily1()
self._collection.GetFontFamily(self._font_index, byref(font_family))
write_font = IDWriteFont()
font_family.GetFirstMatchingFont(
self._weight,
self._stretch,
self._style,
byref(write_font)
)
# Create the text format this font will use permanently.
# Could technically be recreated, but will keep to be inline with other font objects.
self._text_format = IDWriteTextFormat()
@ -1979,18 +2073,6 @@ class Win32DirectWriteFont(base.Font):
byref(self._text_format)
)
# All this work just to get a font face and its metrics!
font_family = IDWriteFontFamily1()
self._collection.GetFontFamily(self._font_index, byref(font_family))
write_font = IDWriteFont()
font_family.GetFirstMatchingFont(
self._weight,
self._stretch,
self._style,
byref(write_font)
)
font_face = IDWriteFontFace()
write_font.CreateFontFace(byref(font_face))
@ -2013,7 +2095,54 @@ class Win32DirectWriteFont(base.Font):
self._fallback = IDWriteFontFallback()
self._write_factory.GetSystemFontFallback(byref(self._fallback))
else:
assert _debug_font("Windows 8.1+ is required for font fallback. Colored glyphs cannot be omitted.")
assert _debug_print("Windows 8.1+ is required for font fallback. Colored glyphs cannot be omitted.")
@property
def filename(self):
"""Returns a filename associated with the font face.
Note: Capable of returning more than 1 file in the future, but will do just one for now."""
if self._filename is not None:
return self._filename
file_ct = UINT32()
self.font_face.GetFiles(byref(file_ct), None)
font_files = (IDWriteFontFile * file_ct.value)()
self.font_face.GetFiles(byref(file_ct), font_files)
self._filename = "Not Available"
pff = font_files[0]
key_data = c_void_p()
ff_key_size = UINT32()
pff.GetReferenceKey(byref(key_data), byref(ff_key_size))
loader = IDWriteFontFileLoader()
pff.GetLoader(byref(loader))
try:
local_loader = IDWriteLocalFontFileLoader()
loader.QueryInterface(IID_IDWriteLocalFontFileLoader, byref(local_loader))
except OSError: # E_NOTIMPL
loader.Release()
pff.Release()
return self._filename
path_len = UINT32()
local_loader.GetFilePathLengthFromKey(key_data, ff_key_size, byref(path_len))
buffer = create_unicode_buffer(path_len.value + 1)
local_loader.GetFilePathFromKey(key_data, ff_key_size, buffer, len(buffer))
loader.Release()
local_loader.Release()
pff.Release()
self._filename = pathlib.PureWindowsPath(buffer.value).as_posix() # Convert to forward slashes.
return self._filename
@property
def name(self):
@ -2176,7 +2305,7 @@ class Win32DirectWriteFont(base.Font):
for idx in clusters:
ct = formatted_clusters.count(idx)
if ct > 1:
substitutions[idx] = ct-1
substitutions[idx] = ct - 1
for i in range(actual_count):
indice = indices[i]
@ -2258,7 +2387,7 @@ class Win32DirectWriteFont(base.Font):
# Note: RegisterFontLoader takes a pointer. However, for legacy we implement our own callback interface.
# Therefore we need to pass to the actual pointer directly.
cls._write_factory.RegisterFontFileLoader(cls._font_loader.pointers[IDWriteFontFileLoader])
cls._write_factory.RegisterFontFileLoader(cls._font_loader.pointers[IDWriteFontFileLoader_LI])
cls._font_collection_loader = LegacyCollectionLoader(cls._write_factory, cls._font_loader)
cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_loader)
@ -2297,6 +2426,7 @@ class Win32DirectWriteFont(base.Font):
cls._custom_collection = IDWriteFontCollection1()
cls._write_factory.CreateFontCollectionFromFontSet(cls._font_set, byref(cls._custom_collection))
else:
cls._font_cache.append(data)
@ -2311,7 +2441,7 @@ class Win32DirectWriteFont(base.Font):
cls._font_collection_loader = LegacyCollectionLoader(cls._write_factory, cls._font_loader)
cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_loader)
cls._write_factory.RegisterFontFileLoader(cls._font_loader.pointers[IDWriteFontFileLoader])
cls._write_factory.RegisterFontFileLoader(cls._font_loader.pointers[IDWriteFontFileLoader_LI])
cls._font_collection_loader.AddFontData(cls._font_cache)
@ -2323,7 +2453,7 @@ class Win32DirectWriteFont(base.Font):
byref(cls._custom_collection))
@classmethod
def get_collection(cls, font_name):
def get_collection(cls, font_name) -> Tuple[Optional[int], Optional[IDWriteFontCollection1]]:
"""Returns which collection this font belongs to (system or custom collection), as well as its index in the
collection."""
if not cls._write_factory:
@ -2344,8 +2474,8 @@ class Win32DirectWriteFont(base.Font):
# Check if font is in the system collection.
# Do not cache these values permanently as system font collection can be updated during runtime.
sys_collection = IDWriteFontCollection()
if not font_exists.value:
sys_collection = IDWriteFontCollection()
cls._write_factory.GetSystemFontCollection(byref(sys_collection), 1)
sys_collection.FindFamilyName(create_unicode_buffer(font_name),
byref(font_index),
@ -2354,57 +2484,245 @@ class Win32DirectWriteFont(base.Font):
if font_exists.value:
return font_index.value, sys_collection
# Font does not exist in either custom or system.
return None, None
@classmethod
def have_font(cls, name):
def find_font_face(cls, font_name, bold, italic, stretch) -> Tuple[
Optional[IDWriteFont], Optional[IDWriteFontCollection]]:
"""This will search font collections for legacy RBIZ names. However, matching to bold, italic, stretch is
problematic in that there are many values. We parse the font name looking for matches to the name database,
and attempt to pick the closest match.
This will search all fonts on the system and custom loaded, and all of their font faces. Returns a collection
and IDWriteFont if successful.
"""
p_bold, p_italic, p_stretch = cls.parse_name(font_name, bold, italic, stretch)
_debug_print(f"directwrite: '{font_name}' not found. Attempting legacy name lookup in all collections.")
collection_idx = cls.find_legacy_font(cls._custom_collection, font_name, p_bold, p_italic, p_stretch)
if collection_idx is not None:
return collection_idx, cls._custom_collection
sys_collection = IDWriteFontCollection()
cls._write_factory.GetSystemFontCollection(byref(sys_collection), 1)
collection_idx = cls.find_legacy_font(sys_collection, font_name, p_bold, p_italic, p_stretch)
if collection_idx is not None:
return collection_idx, sys_collection
return None, None
@classmethod
def have_font(cls, name: str):
if cls.get_collection(name)[0] is not None:
return True
return False
@classmethod
def get_font_face(cls, name):
# Check custom collection.
collection = None
font_index = UINT()
font_exists = BOOL()
@staticmethod
def parse_name(font_name: str, weight: int, style: int, stretch: int):
"""Attempt at parsing any special names in a font for legacy checks. Takes the first found."""
# Check custom collection.
if cls._custom_collection:
cls._custom_collection.FindFamilyName(create_unicode_buffer(name),
byref(font_index),
byref(font_exists))
font_name = font_name.lower()
split_name = font_name.split(' ')
collection = cls._custom_collection
found_weight = weight
found_style = style
found_stretch = stretch
if font_exists.value == 0:
sys_collection = IDWriteFontCollection()
cls._write_factory.GetSystemFontCollection(byref(sys_collection), 1)
sys_collection.FindFamilyName(create_unicode_buffer(name),
byref(font_index),
byref(font_exists))
# Only search if name is split more than once.
if len(split_name) > 1:
for name, value in name_to_weight.items():
if name in split_name:
found_weight = value
break
collection = sys_collection
for name, value in name_to_style.items():
if name in split_name:
found_style = value
break
if font_exists:
font_family = IDWriteFontFamily()
collection.GetFontFamily(font_index, byref(font_family))
for name, value in name_to_stretch.items():
if name in split_name:
found_stretch = value
break
write_font = IDWriteFont()
font_family.GetFirstMatchingFont(DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
byref(write_font))
return found_weight, found_style, found_stretch
font_face = IDWriteFontFace1()
write_font.CreateFontFace(byref(font_face))
@staticmethod
def find_legacy_font(collection: IDWriteFontCollection, font_name: str, bold, italic, stretch, full_debug=False) -> \
Optional[IDWriteFont]:
coll_count = collection.GetFontFamilyCount()
return font_face
assert _debug_print(f"directwrite: Found {coll_count} fonts in collection.")
locale = get_system_locale()
for i in range(coll_count):
family = IDWriteFontFamily()
collection.GetFontFamily(i, byref(family))
# Just check the first character in Family Names to reduce search time. Arial -> A's only.
family_name_str = IDWriteLocalizedStrings()
family.GetFamilyNames(byref(family_name_str))
family_names = Win32DirectWriteFont.unpack_localized_string(family_name_str, locale)
family_name = family_names[0]
if family_name[0] != font_name[0]:
family.Release()
continue
assert _debug_print(f"directwrite: Inspecting family name: {family_name}")
# Fonts in the family. Full search to search all font faces, typically the first will be good enough to tell
ft_ct = family.GetFontCount()
face_names = []
matches = []
for j in range(ft_ct):
temp_ft = IDWriteFont()
family.GetFont(j, byref(temp_ft))
if _debug_font and full_debug:
fc_str = IDWriteLocalizedStrings()
temp_ft.GetFaceNames(byref(fc_str))
strings = Win32DirectWriteFont.unpack_localized_string(fc_str, locale)
face_names.extend(strings)
print(f"directwrite: Face names found: {strings}")
# Check for GDI compatibility name
compat_names = IDWriteLocalizedStrings()
exists = BOOL()
temp_ft.GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES,
byref(compat_names),
byref(exists))
# Successful in finding GDI name.
match_found = False
if exists.value != 0:
for compat_name in Win32DirectWriteFont.unpack_localized_string(compat_names, locale):
if compat_name == font_name:
assert _debug_print(
f"Found legacy name '{font_name}' as '{family_name}' in font face '{j}' (collection id #{i}).")
match_found = True
matches.append((temp_ft.GetWeight(), temp_ft.GetStyle(), temp_ft.GetStretch(), temp_ft))
break
# Release resource if not a match.
if not match_found:
temp_ft.Release()
family.Release()
# If we have matches, we've already parsed through the proper family. Now try to match.
if matches:
write_font = Win32DirectWriteFont.match_closest_font(matches, bold, italic, stretch)
# Cleanup other matches not used.
for match in matches:
if match[3] != write_font:
match[3].Release() # Release all other matches.
return write_font
return None
@staticmethod
def match_closest_font(font_list: List[Tuple[int, int, int, IDWriteFont]], bold: int, italic: int, stretch: int) -> \
Optional[IDWriteFont]:
"""Match the closest font to the parameters specified. If a full match is not found, a secondary match will be
found based on similar features. This can probably be improved, but it is possible you could get a different
font style than expected."""
closest = []
for match in font_list:
(f_weight, f_style, f_stretch, writefont) = match
# Found perfect match, no need for the rest.
if f_weight == bold and f_style == italic and f_stretch == stretch:
_debug_print(
f"directwrite: full match found. (bold: {f_weight}, italic: {f_style}, stretch: {f_stretch})")
return writefont
prop_match = 0
similar_match = 0
# Look for a full match, otherwise look for close enough.
# For example, Arial Black only has Oblique, not Italic, but good enough if you want slanted text.
if f_weight == bold:
prop_match += 1
elif bold != DWRITE_FONT_WEIGHT_NORMAL and f_weight != DWRITE_FONT_WEIGHT_NORMAL:
similar_match += 1
if f_style == italic:
prop_match += 1
elif italic != DWRITE_FONT_STYLE_NORMAL and f_style != DWRITE_FONT_STYLE_NORMAL:
similar_match += 1
if stretch == f_stretch:
prop_match += 1
elif stretch != DWRITE_FONT_STRETCH_NORMAL and f_stretch != DWRITE_FONT_STRETCH_NORMAL:
similar_match += 1
closest.append((prop_match, similar_match, *match))
# If we get here, no perfect match, sort by highest perfect match, to secondary matches.
closest.sort(key=lambda fts: (fts[0], fts[1]), reverse=True)
if closest:
# Take the first match after sorting.
closest_match = closest[0]
_debug_print(f"directwrite: falling back to partial match. "
f"(bold: {closest_match[2]}, italic: {closest_match[3]}, stretch: {closest_match[4]})")
return closest_match[5]
return None
@staticmethod
def unpack_localized_string(local_string: IDWriteLocalizedStrings, locale: str) -> List[str]:
"""Takes IDWriteLocalizedStrings and unpacks the strings inside of it into a list."""
str_array_len = local_string.GetCount()
strings = []
for _ in range(str_array_len):
string_size = UINT32()
idx = Win32DirectWriteFont.get_localized_index(local_string, locale)
local_string.GetStringLength(idx, byref(string_size))
buffer_size = string_size.value
buffer = create_unicode_buffer(buffer_size + 1)
local_string.GetString(idx, buffer, len(buffer))
strings.append(buffer.value)
local_string.Release()
return strings
@staticmethod
def get_localized_index(strings: IDWriteLocalizedStrings, locale: str):
idx = UINT32()
exists = BOOL()
if locale:
strings.FindLocaleName(locale, byref(idx), byref(exists))
if not exists.value:
# fallback to english.
strings.FindLocaleName('en-us', byref(idx), byref(exists))
if not exists:
return 0
return idx.value
return 0
d2d_factory = ID2D1Factory()
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_ID2D1Factory, None, byref(d2d_factory))

View File

@ -194,10 +194,9 @@ class CocoaConfig(Config):
elif _os_x_version >= os_x_release['lion']:
# check for opengl profile
# This requires OS-X Lion (Darwin 11) or higher
version = (
getattr(self, 'major_version', None) or 2,
getattr(self, 'minor_version', None)
)
version = (getattr(self, 'major_version', None) or 3,
getattr(self, 'minor_version', None) or 3)
# tell os-x we want to request a profile
attrs.append(cocoapy.NSOpenGLPFAOpenGLProfile)

View File

@ -5101,3 +5101,5 @@ DWM_BB_TRANSITIONONMAXIMIZED = 0x00000004
STREAM_SEEK_SET = 0
STREAM_SEEK_CUR = 1
STREAM_SEEK_END = 2
LOCALE_NAME_MAX_LENGTH = 85

View File

@ -41,6 +41,9 @@ supported. Helper methods are included for rotating, scaling, and
transforming. The :py:class:`~pyglet.matrix.Mat4` includes class methods
for creating orthographic and perspective projection matrixes.
Matrices behave just like they do in GLSL: they are specified in column-major
order and multiply on the left of vectors, which are treated as columns.
:note: For performance, Matrixes subclass the `tuple` type. They
are therefore immutable - all operations return a new object;
the object is not updated in-place.
@ -78,9 +81,6 @@ class Vec2:
yield self.x
yield self.y
def __len__(self) -> int:
return 2
@_typing.overload
def __getitem__(self, item: int) -> float:
...
@ -92,6 +92,16 @@ class Vec2:
def __getitem__(self, item):
return (self.x, self.y)[item]
def __setitem__(self, key, value):
if type(key) is slice:
for i, attr in enumerate(['x', 'y'][key]):
setattr(self, attr, value[i])
else:
setattr(self, ['x', 'y'][key], value)
def __len__(self) -> int:
return 2
def __add__(self, other: Vec2) -> Vec2:
return Vec2(self.x + other.x, self.y + other.y)
@ -315,6 +325,13 @@ class Vec3:
def __getitem__(self, item):
return (self.x, self.y, self.z)[item]
def __setitem__(self, key, value):
if type(key) is slice:
for i, attr in enumerate(['x', 'y', 'z'][key]):
setattr(self, attr, value[i])
else:
setattr(self, ['x', 'y', 'z'][key], value)
def __len__(self) -> int:
return 3
@ -520,6 +537,13 @@ class Vec4:
def __getitem__(self, item):
return (self.x, self.y, self.z, self.w)[item]
def __setitem__(self, key, value):
if type(key) is slice:
for i, attr in enumerate(['x', 'y', 'z', 'w'][key]):
setattr(self, attr, value[i])
else:
setattr(self, ['x', 'y', 'z', 'w'][key], value)
def __len__(self) -> int:
return 4
@ -696,30 +720,30 @@ class Mat3(tuple):
def __matmul__(self, other):
if isinstance(other, Vec3):
# Columns:
c0 = self[0::3]
c1 = self[1::3]
c2 = self[2::3]
return Vec3(sum(map(_mul, c0, other)),
sum(map(_mul, c1, other)),
sum(map(_mul, c2, other)))
# Rows:
r0 = self[0::3]
r1 = self[1::3]
r2 = self[2::3]
return Vec3(sum(map(_mul, r0, other)),
sum(map(_mul, r1, other)),
sum(map(_mul, r2, other)))
if not isinstance(other, Mat3):
raise TypeError("Can only multiply with Mat3 or Vec3 types")
# Rows:
r0 = self[0:3]
r1 = self[3:6]
r2 = self[6:9]
r0 = self[0::3]
r1 = self[1::3]
r2 = self[2::3]
# Columns:
c0 = other[0::3]
c1 = other[1::3]
c2 = other[2::3]
c0 = other[0:3]
c1 = other[3:6]
c2 = other[6:9]
# Multiply and sum rows * colums:
return Mat3((sum(map(_mul, r0, c0)), sum(map(_mul, r0, c1)), sum(map(_mul, r0, c2)),
sum(map(_mul, r1, c0)), sum(map(_mul, r1, c1)), sum(map(_mul, r1, c2)),
sum(map(_mul, r2, c0)), sum(map(_mul, r2, c1)), sum(map(_mul, r2, c2))))
# Multiply and sum rows * columns:
return Mat3((sum(map(_mul, c0, r0)), sum(map(_mul, c0, r1)), sum(map(_mul, c0, r2)),
sum(map(_mul, c1, r0)), sum(map(_mul, c1, r1)), sum(map(_mul, c1, r2)),
sum(map(_mul, c2, r0)), sum(map(_mul, c2, r1)), sum(map(_mul, c2, r2))))
def __repr__(self) -> str:
return f"{self.__class__.__name__}{self[0:3]}\n {self[3:6]}\n {self[6:9]}"
@ -765,7 +789,11 @@ class Mat4(tuple):
z_near: float,
z_far: float
) -> Mat4T:
"""Create a Mat4 orthographic projection matrix."""
"""Create a Mat4 orthographic projection matrix for use with OpenGL.
This matrix doesn't actually perform the projection; it transforms the
space so that OpenGL's vertex processing performs it.
"""
width = right - left
height = top - bottom
depth = z_far - z_near
@ -792,7 +820,10 @@ class Mat4(tuple):
fov: float = 60
) -> Mat4T:
"""
Create a Mat4 perspective projection matrix.
Create a Mat4 perspective projection matrix for use with OpenGL.
This matrix doesn't actually perform the projection; it transforms the
space so that OpenGL's vertex processing performs it.
:Parameters:
`aspect` : The aspect ratio as a `float`
@ -857,17 +888,6 @@ class Mat4(tuple):
0.0, 0.0, 1.0, 0.0,
vector[0], vector[1], vector[2], 1.0))
@classmethod
def look_at_direction(cls: type[Mat4T], direction: Vec3, up: Vec3) -> Mat4T:
vec_z = direction.normalize()
vec_x = direction.cross(up).normalize()
vec_y = direction.cross(vec_z).normalize()
return cls((vec_x.x, vec_y.x, vec_z.x, 0.0,
vec_x.y, vec_y.y, vec_z.y, 0.0,
vec_x.z, vec_z.z, vec_z.z, 0.0,
0.0, 0.0, 0.0, 1.0))
@classmethod
def look_at(cls: type[Mat4T], position: Vec3, target: Vec3, up: Vec3):
f = (target - position).normalize()
@ -882,11 +902,11 @@ class Mat4(tuple):
def row(self, index: int) -> tuple:
"""Get a specific row as a tuple."""
return self[index * 4: index * 4 + 4]
return self[index::4]
def column(self, index: int) -> tuple:
"""Get a specific column as a tuple."""
return self[index::4]
return self[index * 4: index * 4 + 4]
def rotate(self, angle: float, vector: Vec3) -> Mat4:
"""Get a rotation Matrix on x, y, or z axis."""
@ -1012,34 +1032,34 @@ class Mat4(tuple):
def __matmul__(self, other):
if isinstance(other, Vec4):
# Columns:
c0 = self[0::4]
c1 = self[1::4]
c2 = self[2::4]
c3 = self[3::4]
return Vec4(sum(map(_mul, c0, other)),
sum(map(_mul, c1, other)),
sum(map(_mul, c2, other)),
sum(map(_mul, c3, other)))
# Rows:
r0 = self[0::4]
r1 = self[1::4]
r2 = self[2::4]
r3 = self[3::4]
return Vec4(sum(map(_mul, r0, other)),
sum(map(_mul, r1, other)),
sum(map(_mul, r2, other)),
sum(map(_mul, r3, other)))
if not isinstance(other, Mat4):
raise TypeError("Can only multiply with Mat4 or Vec4 types")
# Rows:
r0 = self[0:4]
r1 = self[4:8]
r2 = self[8:12]
r3 = self[12:16]
r0 = self[0::4]
r1 = self[1::4]
r2 = self[2::4]
r3 = self[3::4]
# Columns:
c0 = other[0::4]
c1 = other[1::4]
c2 = other[2::4]
c3 = other[3::4]
c0 = other[0:4]
c1 = other[4:8]
c2 = other[8:12]
c3 = other[12:16]
# Multiply and sum rows * columns:
return Mat4((sum(map(_mul, r0, c0)), sum(map(_mul, r0, c1)), sum(map(_mul, r0, c2)), sum(map(_mul, r0, c3)),
sum(map(_mul, r1, c0)), sum(map(_mul, r1, c1)), sum(map(_mul, r1, c2)), sum(map(_mul, r1, c3)),
sum(map(_mul, r2, c0)), sum(map(_mul, r2, c1)), sum(map(_mul, r2, c2)), sum(map(_mul, r2, c3)),
sum(map(_mul, r3, c0)), sum(map(_mul, r3, c1)), sum(map(_mul, r3, c2)), sum(map(_mul, r3, c3))))
return Mat4((sum(map(_mul, c0, r0)), sum(map(_mul, c0, r1)), sum(map(_mul, c0, r2)), sum(map(_mul, c0, r3)),
sum(map(_mul, c1, r0)), sum(map(_mul, c1, r1)), sum(map(_mul, c1, r2)), sum(map(_mul, c1, r3)),
sum(map(_mul, c2, r0)), sum(map(_mul, c2, r1)), sum(map(_mul, c2, r2)), sum(map(_mul, c2, r3)),
sum(map(_mul, c3, r0)), sum(map(_mul, c3, r1)), sum(map(_mul, c3, r2)), sum(map(_mul, c3, r3))))
# def __getitem__(self, item):
# row = [slice(0, 4), slice(4, 8), slice(8, 12), slice(12, 16)][item]

View File

@ -215,6 +215,7 @@ class ShapeBase(ABC):
_group = None
_num_verts = 0
_vertex_list = None
_draw_mode = GL_TRIANGLES
def __del__(self):
if self._vertex_list is not None:
@ -233,6 +234,21 @@ class ShapeBase(ABC):
def _update_translation(self):
self._vertex_list.translation[:] = (self._x, self._y) * self._num_verts
def _create_vertex_list(self):
"""Build internal vertex list.
This method must create a vertex list and assign it to
`self._vertex_list`. It is advisable to use it
during `__init__` and to then update the vertices accordingly
with `self._update_vertices`.
While it is not mandatory to implement it, some properties (
namely `batch` and `group`) rely on this method to properly
recreate the vertex list.
"""
raise NotImplementedError('_create_vertex_list must be defined in '
'order to use group or batch properties')
@abstractmethod
def _update_vertices(self):
"""
@ -253,7 +269,7 @@ class ShapeBase(ABC):
shape to a `pyglet.graphics.Batch` for efficient rendering.
"""
self._group.set_state_recursive()
self._vertex_list.draw(GL_TRIANGLES)
self._vertex_list.draw(self._draw_mode)
self._group.unset_state_recursive()
def delete(self):
@ -404,8 +420,45 @@ class ShapeBase(ABC):
self._visible = value
self._update_vertices()
@property
def group(self):
"""User assigned :class:`Group` object."""
return self._group.parent
@group.setter
def group(self, group):
if self._group.parent == group:
return
self._group = _ShapeGroup(self._group.blend_src,
self._group.blend_dest,
self._group.program,
group)
self._batch.migrate(self._vertex_list, self._draw_mode, self._group,
self._batch)
@property
def batch(self):
"""User assigned :class:`Batch` object."""
return self._batch
@batch.setter
def batch(self, batch):
if self._batch == batch:
return
if batch is not None and self._batch is not None:
self._batch.migrate(self._vertex_list, self._draw_mode,
self._group, batch)
self._batch = batch
else:
self._vertex_list.delete()
self._batch = batch
self._create_vertex_list()
class Arc(ShapeBase):
_draw_mode = GL_LINES
def __init__(self, x, y, radius, segments=None, angle=math.tau, start_angle=0,
closed=False, color=(255, 255, 255, 255), batch=None, group=None):
"""Create an Arc.
@ -460,11 +513,15 @@ class Arc(ShapeBase):
program = get_default_shader()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._vertex_list = program.vertex_list(self._num_verts, GL_LINES, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._create_vertex_list()
self._update_vertices()
def _create_vertex_list(self):
self._vertex_list = self._group.program.vertex_list(
self._num_verts, self._draw_mode, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (self._x, self._y) * self._num_verts))
def _update_vertices(self):
if not self._visible:
vertices = (0,) * (self._segments + 1) * 4
@ -539,7 +596,7 @@ class Arc(ShapeBase):
Using this method is not recommended. Instead, add the
shape to a `pyglet.graphics.Batch` for efficient rendering.
"""
self._vertex_list.draw(GL_LINES)
self._vertex_list.draw(self._draw_mode)
class Circle(ShapeBase):
@ -582,11 +639,15 @@ class Circle(ShapeBase):
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._vertex_list = program.vertex_list(self._segments*3, GL_TRIANGLES, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._create_vertex_list()
self._update_vertices()
def _create_vertex_list(self):
self._vertex_list = self._group.program.vertex_list(
self._segments*3, self._draw_mode, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (self._x, self._y) * self._num_verts))
def _update_vertices(self):
if not self._visible:
vertices = (0,) * self._segments * 6
@ -623,6 +684,8 @@ class Circle(ShapeBase):
class Ellipse(ShapeBase):
_draw_mode = GL_LINES
def __init__(self, x, y, a, b, color=(255, 255, 255, 255),
batch=None, group=None):
"""Create an ellipse.
@ -665,11 +728,15 @@ class Ellipse(ShapeBase):
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._vertex_list = program.vertex_list(self._num_verts, GL_LINES, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._create_vertex_list()
self._update_vertices()
def _create_vertex_list(self):
self._vertex_list = self._group.program.vertex_list(
self._num_verts, self._draw_mode, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (self._x, self._y) * self._num_verts))
def _update_vertices(self):
if not self._visible:
vertices = (0,) * self._num_verts * 4
@ -738,7 +805,7 @@ class Ellipse(ShapeBase):
Using this method is not recommended. Instead, add the
shape to a `pyglet.graphics.Batch` for efficient rendering.
"""
self._vertex_list.draw(GL_LINES)
self._vertex_list.draw(self._draw_mode)
class Sector(ShapeBase):
@ -791,11 +858,15 @@ class Sector(ShapeBase):
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._vertex_list = program.vertex_list(self._num_verts, GL_TRIANGLES, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._create_vertex_list()
self._update_vertices()
def _create_vertex_list(self):
self._vertex_list = self._group.program.vertex_list(
self._num_verts, self._draw_mode, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (self._x, self._y) * self._num_verts))
def _update_vertices(self):
if not self._visible:
vertices = (0,) * self._segments * 6
@ -918,11 +989,15 @@ class Line(ShapeBase):
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._vertex_list = program.vertex_list(6, GL_TRIANGLES, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._create_vertex_list()
self._update_vertices()
def _create_vertex_list(self):
self._vertex_list = self._group.program.vertex_list(
6, self._draw_mode, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (self._x, self._y) * self._num_verts))
def _update_vertices(self):
if not self._visible:
self._vertex_list.vertices[:] = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
@ -1013,11 +1088,15 @@ class Rectangle(ShapeBase):
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._vertex_list = program.vertex_list(6, GL_TRIANGLES, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._create_vertex_list()
self._update_vertices()
def _create_vertex_list(self):
self._vertex_list = self._group.program.vertex_list(
6, self._draw_mode, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (self._x, self._y) * self._num_verts))
def _update_vertices(self):
if not self._visible:
self._vertex_list.vertices[:] = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
@ -1143,12 +1222,16 @@ class BorderedRectangle(ShapeBase):
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
indices = [0, 1, 2, 0, 2, 3, 0, 4, 3, 4, 7, 3, 0, 1, 5, 0, 5, 4, 1, 2, 5, 5, 2, 6, 6, 2, 3, 6, 3, 7]
self._vertex_list = program.vertex_list_indexed(8, GL_TRIANGLES, indices, self._batch, self._group,
colors=('Bn', self._rgba * 4 + self._border_rgba * 4),
translation=('f', (x, y) * self._num_verts))
self._create_vertex_list()
self._update_vertices()
def _create_vertex_list(self):
indices = [0, 1, 2, 0, 2, 3, 0, 4, 3, 4, 7, 3, 0, 1, 5, 0, 5, 4, 1, 2, 5, 5, 2, 6, 6, 2, 3, 6, 3, 7]
self._vertex_list = self._group.program.vertex_list_indexed(
8, self._draw_mode, indices, self._batch, self._group,
colors=('Bn', self._rgba * 4 + self._border_rgba * 4),
translation=('f', (self._x, self._y) * self._num_verts))
def _update_color(self):
self._vertex_list.colors[:] = self._rgba * 4 + self._border_rgba * 4
@ -1318,11 +1401,15 @@ class Triangle(ShapeBase):
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._vertex_list = program.vertex_list(3, GL_TRIANGLES, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._create_vertex_list()
self._update_vertices()
def _create_vertex_list(self):
self._vertex_list = self._group.program.vertex_list(
3, self._draw_mode, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (self._x, self._y) * self._num_verts))
def _update_vertices(self):
if not self._visible:
self._vertex_list.vertices[:] = (0, 0, 0, 0, 0, 0)
@ -1434,12 +1521,16 @@ class Star(ShapeBase):
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._vertex_list = program.vertex_list(self._num_verts, GL_TRIANGLES, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
rotation=('f', (rotation,) * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._create_vertex_list()
self._update_vertices()
def _create_vertex_list(self):
self._vertex_list = self._group.program.vertex_list(
self._num_verts, self._draw_mode, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
rotation=('f', (self._rotation,) * self._num_verts),
translation=('f', (self._x, self._y) * self._num_verts))
def _update_vertices(self):
if not self._visible:
vertices = (0, 0) * self._num_spikes * 6
@ -1541,12 +1632,16 @@ class Polygon(ShapeBase):
self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group)
self._vertex_list = program.vertex_list(self._num_verts, GL_TRIANGLES, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (coordinates[0]) * self._num_verts))
self._create_vertex_list()
self._update_vertices()
self._update_color()
def _create_vertex_list(self):
self._vertex_list = self._group.program.vertex_list(
self._num_verts, self._draw_mode, self._batch, self._group,
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (self._coordinates[0]) * self._num_verts))
def _update_vertices(self):
if not self._visible:
self._vertex_list.vertices[:] = tuple([0] * ((len(self._coordinates) - 2) * 6))

View File

@ -217,7 +217,7 @@ class InlineElement:
"""
return self._position
def place(self, layout, x, y):
def place(self, layout, x, y, z):
"""Construct an instance of the element at the given coordinates.
Called when the element's position within a layout changes, either

View File

@ -87,8 +87,8 @@ class ImageElement(pyglet.text.document.InlineElement):
descent = min(0, -anchor_y)
super().__init__(ascent, descent, self.width)
def place(self, layout, x, y):
program = pyglet.text.layout.get_default_layout_shader()
def place(self, layout, x, y, z):
program = pyglet.text.layout.get_default_image_layout_shader()
group = _InlineElementGroup(self.image.get_texture(), program, 0, layout.group)
x1 = x
y1 = y + self.descent
@ -96,7 +96,7 @@ class ImageElement(pyglet.text.document.InlineElement):
y2 = y + self.height + self.descent
vertex_list = program.vertex_list_indexed(4, pyglet.gl.GL_TRIANGLES, [0, 1, 2, 0, 2, 3],
layout.batch, group,
position=('f', (x1, y1, x2, y1, x2, y2, x1, y2)),
position=('f', (x1, y1, z, x2, y1, z, x2, y2, z, x1, y2, z)),
tex_coords=('f', self.image.tex_coords))
self.vertex_lists[layout] = vertex_list

View File

@ -602,6 +602,31 @@ layout_fragment_source = """#version 330 core
}
"""
layout_fragment_image_source = """#version 330 core
in vec4 text_colors;
in vec2 texture_coords;
in vec4 vert_position;
uniform sampler2D image_texture;
out vec4 final_colors;
uniform sampler2D text;
uniform bool scissor;
uniform vec4 scissor_area;
void main()
{
final_colors = texture(image_texture, texture_coords.xy);
if (scissor == true) {
if (vert_position.x < scissor_area[0]) discard; // left
if (vert_position.y < scissor_area[1]) discard; // bottom
if (vert_position.x > scissor_area[0] + scissor_area[2]) discard; // right
if (vert_position.y > scissor_area[1] + scissor_area[3]) discard; // top
}
}
"""
decoration_vertex_source = """#version 330 core
in vec3 position;
in vec4 colors;
@ -673,6 +698,17 @@ def get_default_layout_shader():
return pyglet.gl.current_context.pyglet_text_layout_shader
def get_default_image_layout_shader():
try:
return pyglet.gl.current_context.pyglet_text_layout_image_shader
except AttributeError:
pyglet.gl.current_context.pyglet_text_layout_image_shader = shader.ShaderProgram(
shader.Shader(layout_vertex_source, 'vertex'),
shader.Shader(layout_fragment_image_source, 'fragment'),
)
return pyglet.gl.current_context.pyglet_text_layout_image_shader
def get_default_decoration_shader():
try:
return pyglet.gl.current_context.pyglet_text_decoration_shader