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': False,
'headless_device': 0, 'headless_device': 0,
'win32_disable_shaping': False, 'win32_disable_shaping': False,
'dw_legacy_naming': False
} }
_option_types = { _option_types = {
@ -192,6 +193,7 @@ _option_types = {
'headless': bool, 'headless': bool,
'headless_device': int, 'headless_device': int,
'win32_disable_shaping': bool, 'win32_disable_shaping': bool,
'dw_legacy_naming': bool
} }

View File

@ -1,22 +1,18 @@
import copy import copy
import os
import pathlib
import platform
from ctypes import *
from typing import List, Optional, Tuple
import math
import pyglet import pyglet
from pyglet.font import base from pyglet.font import base
from pyglet.util import debug_print from pyglet.image.codecs.wic import IWICBitmap, WICDecoder, GUID_WICPixelFormat32bppPBGRA
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.libs.win32 import _kernel32 as kernel32 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.constants import *
from pyglet.libs.win32.types import * from pyglet.libs.win32.types import *
from ctypes import * from pyglet.util import debug_print
import os
import platform
try: try:
dwrite = 'dwrite' dwrite = 'dwrite'
@ -33,7 +29,9 @@ except OSError as err:
# Doesn't exist? Should stop import of library. # Doesn't exist? Should stop import of library.
pass 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): 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, "semiexpanded": DWRITE_FONT_STRETCH_SEMI_EXPANDED,
"expanded": DWRITE_FONT_STRETCH_EXPANDED, "expanded": DWRITE_FONT_STRETCH_EXPANDED,
"extraexpanded": DWRITE_FONT_STRETCH_EXTRA_EXPANDED, "extraexpanded": DWRITE_FONT_STRETCH_EXTRA_EXPANDED,
"narrow": DWRITE_FONT_STRETCH_CONDENSED,
} }
DWRITE_GLYPH_IMAGE_FORMATS = c_int DWRITE_GLYPH_IMAGE_FORMATS = c_int
@ -146,6 +145,34 @@ INT32 = c_int32
UINT32 = c_uint32 UINT32 = c_uint32
UINT64 = c_uint64 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): class D2D_POINT_2F(Structure):
_fields_ = ( _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): class IDWriteFontFace(com.pIUnknown):
_methods_ = [ _methods_ = [
('GetType', ('GetType',
com.STDMETHOD()), com.STDMETHOD()),
('GetFiles', ('GetFiles',
com.STDMETHOD()), com.STDMETHOD(POINTER(UINT32), POINTER(IDWriteFontFile))),
('GetIndex', ('GetIndex',
com.STDMETHOD()), com.STDMETHOD()),
('GetSimulations', ('GetSimulations',
@ -582,9 +661,9 @@ class IDWriteFontList(com.pIUnknown):
('GetFontCollection', ('GetFontCollection',
com.STDMETHOD()), com.STDMETHOD()),
('GetFontCount', ('GetFontCount',
com.STDMETHOD()), com.METHOD(UINT32)),
('GetFont', ('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): class IDWriteFont(com.pIUnknown):
_methods_ = [ _methods_ = [
('GetFontFamily', ('GetFontFamily',
com.STDMETHOD(POINTER(IDWriteFontFamily))), com.STDMETHOD(POINTER(IDWriteFontFamily))),
('GetWeight', ('GetWeight',
com.STDMETHOD()), com.METHOD(DWRITE_FONT_WEIGHT)),
('GetStretch', ('GetStretch',
com.STDMETHOD()), com.METHOD(DWRITE_FONT_STRETCH)),
('GetStyle', ('GetStyle',
com.STDMETHOD()), com.METHOD(DWRITE_FONT_STYLE)),
('IsSymbolFont', ('IsSymbolFont',
com.STDMETHOD()), com.METHOD(BOOL)),
('GetFaceNames', ('GetFaceNames',
com.STDMETHOD(POINTER(IDWriteLocalizedStrings))), com.STDMETHOD(POINTER(IDWriteLocalizedStrings))),
('GetInformationalStrings', ('GetInformationalStrings',
com.STDMETHOD()), com.STDMETHOD(DWRITE_INFORMATIONAL_STRING_ID, POINTER(IDWriteLocalizedStrings), POINTER(BOOL))),
('GetSimulations', ('GetSimulations',
com.STDMETHOD()), com.STDMETHOD()),
('GetMetrics', ('GetMetrics',
@ -664,7 +732,7 @@ class IDWriteFont1(IDWriteFont, com.pIUnknown):
class IDWriteFontCollection(com.pIUnknown): class IDWriteFontCollection(com.pIUnknown):
_methods_ = [ _methods_ = [
('GetFontFamilyCount', ('GetFontFamilyCount',
com.STDMETHOD()), com.METHOD(UINT32)),
('GetFontFamily', ('GetFontFamily',
com.STDMETHOD(UINT32, POINTER(IDWriteFontFamily))), com.STDMETHOD(UINT32, POINTER(IDWriteFontFamily))),
('FindFamilyName', ('FindFamilyName',
@ -690,6 +758,21 @@ DWRITE_TEXT_ALIGNMENT_CENTER = 3
DWRITE_TEXT_ALIGNMENT_JUSTIFIED = 4 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): class IDWriteTextFormat(com.pIUnknown):
_methods_ = [ _methods_ = [
('SetTextAlignment', ('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): class MyFontFileStream(com.COMObject):
_interfaces_ = [IDWriteFontFileStream] _interfaces_ = [IDWriteFontFileStream]
@ -938,15 +1008,8 @@ class MyFontFileStream(com.COMObject):
return 0x80004001 # E_NOTIMPL 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): class LegacyFontFileLoader(com.COMObject):
_interfaces_ = [IDWriteFontFileLoader] _interfaces_ = [IDWriteFontFileLoader_LI]
def __init__(self): def __init__(self):
self._streams = {} self._streams = {}
@ -1089,7 +1152,7 @@ class IDWriteFactory(com.pIUnknown):
('CreateFontFileReference', ('CreateFontFileReference',
com.STDMETHOD(c_wchar_p, c_void_p, POINTER(IDWriteFontFile))), com.STDMETHOD(c_wchar_p, c_void_p, POINTER(IDWriteFontFile))),
('CreateCustomFontFileReference', ('CreateCustomFontFileReference',
com.STDMETHOD(c_void_p, UINT32, POINTER(IDWriteFontFileLoader), POINTER(IDWriteFontFile))), com.STDMETHOD(c_void_p, UINT32, POINTER(IDWriteFontFileLoader_LI), POINTER(IDWriteFontFile))),
('CreateFontFace', ('CreateFontFace',
com.STDMETHOD()), com.STDMETHOD()),
('CreateRenderingParams', ('CreateRenderingParams',
@ -1101,14 +1164,14 @@ class IDWriteFactory(com.pIUnknown):
('RegisterFontFileLoader', ('RegisterFontFileLoader',
com.STDMETHOD(c_void_p)), # Ambigious as newer is a pIUnknown and legacy is IUnknown. com.STDMETHOD(c_void_p)), # Ambigious as newer is a pIUnknown and legacy is IUnknown.
('UnregisterFontFileLoader', ('UnregisterFontFileLoader',
com.STDMETHOD(POINTER(IDWriteFontFileLoader))), com.STDMETHOD(POINTER(IDWriteFontFileLoader_LI))),
('CreateTextFormat', ('CreateTextFormat',
com.STDMETHOD(c_wchar_p, IDWriteFontCollection, DWRITE_FONT_WEIGHT, DWRITE_FONT_STYLE, DWRITE_FONT_STRETCH, com.STDMETHOD(c_wchar_p, IDWriteFontCollection, DWRITE_FONT_WEIGHT, DWRITE_FONT_STYLE, DWRITE_FONT_STRETCH,
FLOAT, c_wchar_p, POINTER(IDWriteTextFormat))), FLOAT, c_wchar_p, POINTER(IDWriteTextFormat))),
('CreateTypography', ('CreateTypography',
com.STDMETHOD(POINTER(IDWriteTypography))), com.STDMETHOD(POINTER(IDWriteTypography))),
('GetGdiInterop', ('GetGdiInterop',
com.STDMETHOD()), com.STDMETHOD(POINTER(IDWriteGdiInterop))),
('CreateTextLayout', ('CreateTextLayout',
com.STDMETHOD(c_wchar_p, UINT32, IDWriteTextFormat, FLOAT, FLOAT, POINTER(IDWriteTextLayout))), com.STDMETHOD(c_wchar_p, UINT32, IDWriteTextFormat, FLOAT, FLOAT, POINTER(IDWriteTextLayout))),
('CreateGdiCompatibleTextLayout', ('CreateGdiCompatibleTextLayout',
@ -1548,6 +1611,13 @@ if not wic_decoder:
raise Exception("Cannot use DirectWrite without a 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): class DirectWriteGlyphRenderer(base.GlyphRenderer):
antialias_mode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT 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 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. # Negative LSB: we shift the offset, otherwise the glyph will be cut off.
render_offset_x = glyph_lsb * self.font.font_scale_ratio 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. # Create new bitmap.
# TODO: We can probably adjust bitmap/baseline to reduce the whitespace and save a lot of texture space. # 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. # 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 texture_internalformat = pyglet.gl.GL_RGBA
def __init__(self, name, size, bold=False, italic=False, stretch=False, dpi=None, locale=None): 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. self._advance_cache = {} # Stores glyph's by the indice and advance.
super(Win32DirectWriteFont, self).__init__() super(Win32DirectWriteFont, self).__init__()
@ -1919,9 +1995,6 @@ class Win32DirectWriteFont(base.Font):
if not name: if not name:
name = self._default_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._name = name
self.bold = bold self.bold = bold
self.size = size self.size = size
@ -1965,6 +2038,27 @@ class Win32DirectWriteFont(base.Font):
else: else:
self._stretch = DWRITE_FONT_STRETCH_NORMAL 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. # Create the text format this font will use permanently.
# Could technically be recreated, but will keep to be inline with other font objects. # Could technically be recreated, but will keep to be inline with other font objects.
self._text_format = IDWriteTextFormat() self._text_format = IDWriteTextFormat()
@ -1979,18 +2073,6 @@ class Win32DirectWriteFont(base.Font):
byref(self._text_format) 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() font_face = IDWriteFontFace()
write_font.CreateFontFace(byref(font_face)) write_font.CreateFontFace(byref(font_face))
@ -2013,7 +2095,54 @@ class Win32DirectWriteFont(base.Font):
self._fallback = IDWriteFontFallback() self._fallback = IDWriteFontFallback()
self._write_factory.GetSystemFontFallback(byref(self._fallback)) self._write_factory.GetSystemFontFallback(byref(self._fallback))
else: 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 @property
def name(self): def name(self):
@ -2176,7 +2305,7 @@ class Win32DirectWriteFont(base.Font):
for idx in clusters: for idx in clusters:
ct = formatted_clusters.count(idx) ct = formatted_clusters.count(idx)
if ct > 1: if ct > 1:
substitutions[idx] = ct-1 substitutions[idx] = ct - 1
for i in range(actual_count): for i in range(actual_count):
indice = indices[i] 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. # Note: RegisterFontLoader takes a pointer. However, for legacy we implement our own callback interface.
# Therefore we need to pass to the actual pointer directly. # 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._font_collection_loader = LegacyCollectionLoader(cls._write_factory, cls._font_loader)
cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_loader) cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_loader)
@ -2297,6 +2426,7 @@ class Win32DirectWriteFont(base.Font):
cls._custom_collection = IDWriteFontCollection1() cls._custom_collection = IDWriteFontCollection1()
cls._write_factory.CreateFontCollectionFromFontSet(cls._font_set, byref(cls._custom_collection)) cls._write_factory.CreateFontCollectionFromFontSet(cls._font_set, byref(cls._custom_collection))
else: else:
cls._font_cache.append(data) 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._font_collection_loader = LegacyCollectionLoader(cls._write_factory, cls._font_loader)
cls._write_factory.RegisterFontCollectionLoader(cls._font_collection_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) cls._font_collection_loader.AddFontData(cls._font_cache)
@ -2323,7 +2453,7 @@ class Win32DirectWriteFont(base.Font):
byref(cls._custom_collection)) byref(cls._custom_collection))
@classmethod @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 """Returns which collection this font belongs to (system or custom collection), as well as its index in the
collection.""" collection."""
if not cls._write_factory: if not cls._write_factory:
@ -2344,8 +2474,8 @@ class Win32DirectWriteFont(base.Font):
# Check if font is in the system collection. # Check if font is in the system collection.
# Do not cache these values permanently as system font collection can be updated during runtime. # Do not cache these values permanently as system font collection can be updated during runtime.
sys_collection = IDWriteFontCollection()
if not font_exists.value: if not font_exists.value:
sys_collection = IDWriteFontCollection()
cls._write_factory.GetSystemFontCollection(byref(sys_collection), 1) cls._write_factory.GetSystemFontCollection(byref(sys_collection), 1)
sys_collection.FindFamilyName(create_unicode_buffer(font_name), sys_collection.FindFamilyName(create_unicode_buffer(font_name),
byref(font_index), byref(font_index),
@ -2354,57 +2484,245 @@ class Win32DirectWriteFont(base.Font):
if font_exists.value: if font_exists.value:
return font_index.value, sys_collection return font_index.value, sys_collection
# Font does not exist in either custom or system.
return None, None return None, None
@classmethod @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: if cls.get_collection(name)[0] is not None:
return True return True
return False return False
@classmethod @staticmethod
def get_font_face(cls, name): def parse_name(font_name: str, weight: int, style: int, stretch: int):
# Check custom collection. """Attempt at parsing any special names in a font for legacy checks. Takes the first found."""
collection = None
font_index = UINT()
font_exists = BOOL()
# Check custom collection. font_name = font_name.lower()
if cls._custom_collection: split_name = font_name.split(' ')
cls._custom_collection.FindFamilyName(create_unicode_buffer(name),
byref(font_index),
byref(font_exists))
collection = cls._custom_collection found_weight = weight
found_style = style
found_stretch = stretch
if font_exists.value == 0: # Only search if name is split more than once.
sys_collection = IDWriteFontCollection() if len(split_name) > 1:
cls._write_factory.GetSystemFontCollection(byref(sys_collection), 1) for name, value in name_to_weight.items():
sys_collection.FindFamilyName(create_unicode_buffer(name), if name in split_name:
byref(font_index), found_weight = value
byref(font_exists)) break
collection = sys_collection for name, value in name_to_style.items():
if name in split_name:
found_style = value
break
if font_exists: for name, value in name_to_stretch.items():
font_family = IDWriteFontFamily() if name in split_name:
collection.GetFontFamily(font_index, byref(font_family)) found_stretch = value
break
write_font = IDWriteFont() return found_weight, found_style, found_stretch
font_family.GetFirstMatchingFont(DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
byref(write_font))
font_face = IDWriteFontFace1() @staticmethod
write_font.CreateFontFace(byref(font_face)) 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 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() d2d_factory = ID2D1Factory()
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_ID2D1Factory, None, byref(d2d_factory)) 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']: elif _os_x_version >= os_x_release['lion']:
# check for opengl profile # check for opengl profile
# This requires OS-X Lion (Darwin 11) or higher # This requires OS-X Lion (Darwin 11) or higher
version = ( version = (getattr(self, 'major_version', None) or 3,
getattr(self, 'major_version', None) or 2, getattr(self, 'minor_version', None) or 3)
getattr(self, 'minor_version', None)
)
# tell os-x we want to request a profile # tell os-x we want to request a profile
attrs.append(cocoapy.NSOpenGLPFAOpenGLProfile) attrs.append(cocoapy.NSOpenGLPFAOpenGLProfile)

View File

@ -5101,3 +5101,5 @@ DWM_BB_TRANSITIONONMAXIMIZED = 0x00000004
STREAM_SEEK_SET = 0 STREAM_SEEK_SET = 0
STREAM_SEEK_CUR = 1 STREAM_SEEK_CUR = 1
STREAM_SEEK_END = 2 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 transforming. The :py:class:`~pyglet.matrix.Mat4` includes class methods
for creating orthographic and perspective projection matrixes. 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 :note: For performance, Matrixes subclass the `tuple` type. They
are therefore immutable - all operations return a new object; are therefore immutable - all operations return a new object;
the object is not updated in-place. the object is not updated in-place.
@ -78,9 +81,6 @@ class Vec2:
yield self.x yield self.x
yield self.y yield self.y
def __len__(self) -> int:
return 2
@_typing.overload @_typing.overload
def __getitem__(self, item: int) -> float: def __getitem__(self, item: int) -> float:
... ...
@ -92,6 +92,16 @@ class Vec2:
def __getitem__(self, item): def __getitem__(self, item):
return (self.x, self.y)[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: def __add__(self, other: Vec2) -> Vec2:
return Vec2(self.x + other.x, self.y + other.y) return Vec2(self.x + other.x, self.y + other.y)
@ -315,6 +325,13 @@ class Vec3:
def __getitem__(self, item): def __getitem__(self, item):
return (self.x, self.y, self.z)[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: def __len__(self) -> int:
return 3 return 3
@ -520,6 +537,13 @@ class Vec4:
def __getitem__(self, item): def __getitem__(self, item):
return (self.x, self.y, self.z, self.w)[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: def __len__(self) -> int:
return 4 return 4
@ -696,30 +720,30 @@ class Mat3(tuple):
def __matmul__(self, other): def __matmul__(self, other):
if isinstance(other, Vec3): if isinstance(other, Vec3):
# Columns: # Rows:
c0 = self[0::3] r0 = self[0::3]
c1 = self[1::3] r1 = self[1::3]
c2 = self[2::3] r2 = self[2::3]
return Vec3(sum(map(_mul, c0, other)), return Vec3(sum(map(_mul, r0, other)),
sum(map(_mul, c1, other)), sum(map(_mul, r1, other)),
sum(map(_mul, c2, other))) sum(map(_mul, r2, other)))
if not isinstance(other, Mat3): if not isinstance(other, Mat3):
raise TypeError("Can only multiply with Mat3 or Vec3 types") raise TypeError("Can only multiply with Mat3 or Vec3 types")
# Rows: # Rows:
r0 = self[0:3] r0 = self[0::3]
r1 = self[3:6] r1 = self[1::3]
r2 = self[6:9] r2 = self[2::3]
# Columns: # Columns:
c0 = other[0::3] c0 = other[0:3]
c1 = other[1::3] c1 = other[3:6]
c2 = other[2::3] c2 = other[6:9]
# Multiply and sum rows * colums: # Multiply and sum rows * columns:
return Mat3((sum(map(_mul, r0, c0)), sum(map(_mul, r0, c1)), sum(map(_mul, r0, c2)), return Mat3((sum(map(_mul, c0, r0)), sum(map(_mul, c0, r1)), sum(map(_mul, c0, r2)),
sum(map(_mul, r1, c0)), sum(map(_mul, r1, c1)), sum(map(_mul, r1, c2)), sum(map(_mul, c1, r0)), sum(map(_mul, c1, r1)), sum(map(_mul, c1, r2)),
sum(map(_mul, r2, c0)), sum(map(_mul, r2, c1)), sum(map(_mul, r2, c2)))) sum(map(_mul, c2, r0)), sum(map(_mul, c2, r1)), sum(map(_mul, c2, r2))))
def __repr__(self) -> str: def __repr__(self) -> str:
return f"{self.__class__.__name__}{self[0:3]}\n {self[3:6]}\n {self[6:9]}" 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_near: float,
z_far: float z_far: float
) -> Mat4T: ) -> 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 width = right - left
height = top - bottom height = top - bottom
depth = z_far - z_near depth = z_far - z_near
@ -792,7 +820,10 @@ class Mat4(tuple):
fov: float = 60 fov: float = 60
) -> Mat4T: ) -> 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: :Parameters:
`aspect` : The aspect ratio as a `float` `aspect` : The aspect ratio as a `float`
@ -857,17 +888,6 @@ class Mat4(tuple):
0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0,
vector[0], vector[1], vector[2], 1.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 @classmethod
def look_at(cls: type[Mat4T], position: Vec3, target: Vec3, up: Vec3): def look_at(cls: type[Mat4T], position: Vec3, target: Vec3, up: Vec3):
f = (target - position).normalize() f = (target - position).normalize()
@ -882,11 +902,11 @@ class Mat4(tuple):
def row(self, index: int) -> tuple: def row(self, index: int) -> tuple:
"""Get a specific row as a 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: def column(self, index: int) -> tuple:
"""Get a specific column as a 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: def rotate(self, angle: float, vector: Vec3) -> Mat4:
"""Get a rotation Matrix on x, y, or z axis.""" """Get a rotation Matrix on x, y, or z axis."""
@ -1012,34 +1032,34 @@ class Mat4(tuple):
def __matmul__(self, other): def __matmul__(self, other):
if isinstance(other, Vec4): if isinstance(other, Vec4):
# Columns: # Rows:
c0 = self[0::4] r0 = self[0::4]
c1 = self[1::4] r1 = self[1::4]
c2 = self[2::4] r2 = self[2::4]
c3 = self[3::4] r3 = self[3::4]
return Vec4(sum(map(_mul, c0, other)), return Vec4(sum(map(_mul, r0, other)),
sum(map(_mul, c1, other)), sum(map(_mul, r1, other)),
sum(map(_mul, c2, other)), sum(map(_mul, r2, other)),
sum(map(_mul, c3, other))) sum(map(_mul, r3, other)))
if not isinstance(other, Mat4): if not isinstance(other, Mat4):
raise TypeError("Can only multiply with Mat4 or Vec4 types") raise TypeError("Can only multiply with Mat4 or Vec4 types")
# Rows: # Rows:
r0 = self[0:4] r0 = self[0::4]
r1 = self[4:8] r1 = self[1::4]
r2 = self[8:12] r2 = self[2::4]
r3 = self[12:16] r3 = self[3::4]
# Columns: # Columns:
c0 = other[0::4] c0 = other[0:4]
c1 = other[1::4] c1 = other[4:8]
c2 = other[2::4] c2 = other[8:12]
c3 = other[3::4] c3 = other[12:16]
# Multiply and sum rows * columns: # 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)), 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, r1, c0)), sum(map(_mul, r1, c1)), sum(map(_mul, r1, c2)), sum(map(_mul, r1, c3)), sum(map(_mul, c1, r0)), sum(map(_mul, c1, r1)), sum(map(_mul, c1, r2)), sum(map(_mul, c1, r3)),
sum(map(_mul, r2, c0)), sum(map(_mul, r2, c1)), sum(map(_mul, r2, c2)), sum(map(_mul, r2, c3)), sum(map(_mul, c2, r0)), sum(map(_mul, c2, r1)), sum(map(_mul, c2, r2)), sum(map(_mul, c2, r3)),
sum(map(_mul, r3, c0)), sum(map(_mul, r3, c1)), sum(map(_mul, r3, c2)), sum(map(_mul, r3, c3)))) sum(map(_mul, c3, r0)), sum(map(_mul, c3, r1)), sum(map(_mul, c3, r2)), sum(map(_mul, c3, r3))))
# def __getitem__(self, item): # def __getitem__(self, item):
# row = [slice(0, 4), slice(4, 8), slice(8, 12), slice(12, 16)][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 _group = None
_num_verts = 0 _num_verts = 0
_vertex_list = None _vertex_list = None
_draw_mode = GL_TRIANGLES
def __del__(self): def __del__(self):
if self._vertex_list is not None: if self._vertex_list is not None:
@ -233,6 +234,21 @@ class ShapeBase(ABC):
def _update_translation(self): def _update_translation(self):
self._vertex_list.translation[:] = (self._x, self._y) * self._num_verts 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 @abstractmethod
def _update_vertices(self): def _update_vertices(self):
""" """
@ -253,7 +269,7 @@ class ShapeBase(ABC):
shape to a `pyglet.graphics.Batch` for efficient rendering. shape to a `pyglet.graphics.Batch` for efficient rendering.
""" """
self._group.set_state_recursive() self._group.set_state_recursive()
self._vertex_list.draw(GL_TRIANGLES) self._vertex_list.draw(self._draw_mode)
self._group.unset_state_recursive() self._group.unset_state_recursive()
def delete(self): def delete(self):
@ -404,8 +420,45 @@ class ShapeBase(ABC):
self._visible = value self._visible = value
self._update_vertices() 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): class Arc(ShapeBase):
_draw_mode = GL_LINES
def __init__(self, x, y, radius, segments=None, angle=math.tau, start_angle=0, 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): closed=False, color=(255, 255, 255, 255), batch=None, group=None):
"""Create an Arc. """Create an Arc.
@ -460,11 +513,15 @@ class Arc(ShapeBase):
program = get_default_shader() program = get_default_shader()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group) 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, self._create_vertex_list()
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._update_vertices() 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): def _update_vertices(self):
if not self._visible: if not self._visible:
vertices = (0,) * (self._segments + 1) * 4 vertices = (0,) * (self._segments + 1) * 4
@ -539,7 +596,7 @@ class Arc(ShapeBase):
Using this method is not recommended. Instead, add the Using this method is not recommended. Instead, add the
shape to a `pyglet.graphics.Batch` for efficient rendering. 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): class Circle(ShapeBase):
@ -582,11 +639,15 @@ class Circle(ShapeBase):
self._batch = batch or Batch() self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group) 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, self._create_vertex_list()
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._update_vertices() 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): def _update_vertices(self):
if not self._visible: if not self._visible:
vertices = (0,) * self._segments * 6 vertices = (0,) * self._segments * 6
@ -623,6 +684,8 @@ class Circle(ShapeBase):
class Ellipse(ShapeBase): class Ellipse(ShapeBase):
_draw_mode = GL_LINES
def __init__(self, x, y, a, b, color=(255, 255, 255, 255), def __init__(self, x, y, a, b, color=(255, 255, 255, 255),
batch=None, group=None): batch=None, group=None):
"""Create an ellipse. """Create an ellipse.
@ -665,11 +728,15 @@ class Ellipse(ShapeBase):
self._batch = batch or Batch() self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group) 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, self._create_vertex_list()
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._update_vertices() 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): def _update_vertices(self):
if not self._visible: if not self._visible:
vertices = (0,) * self._num_verts * 4 vertices = (0,) * self._num_verts * 4
@ -738,7 +805,7 @@ class Ellipse(ShapeBase):
Using this method is not recommended. Instead, add the Using this method is not recommended. Instead, add the
shape to a `pyglet.graphics.Batch` for efficient rendering. 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): class Sector(ShapeBase):
@ -791,11 +858,15 @@ class Sector(ShapeBase):
self._batch = batch or Batch() self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group) 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, self._create_vertex_list()
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._update_vertices() 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): def _update_vertices(self):
if not self._visible: if not self._visible:
vertices = (0,) * self._segments * 6 vertices = (0,) * self._segments * 6
@ -918,11 +989,15 @@ class Line(ShapeBase):
self._batch = batch or Batch() self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group) 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, self._create_vertex_list()
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._update_vertices() 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): def _update_vertices(self):
if not self._visible: if not self._visible:
self._vertex_list.vertices[:] = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) 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._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group) 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, self._create_vertex_list()
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._update_vertices() 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): def _update_vertices(self):
if not self._visible: if not self._visible:
self._vertex_list.vertices[:] = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) 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._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group) 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._create_vertex_list()
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._update_vertices() 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): def _update_color(self):
self._vertex_list.colors[:] = self._rgba * 4 + self._border_rgba * 4 self._vertex_list.colors[:] = self._rgba * 4 + self._border_rgba * 4
@ -1318,11 +1401,15 @@ class Triangle(ShapeBase):
self._batch = batch or Batch() self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group) 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, self._create_vertex_list()
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._update_vertices() 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): def _update_vertices(self):
if not self._visible: if not self._visible:
self._vertex_list.vertices[:] = (0, 0, 0, 0, 0, 0) self._vertex_list.vertices[:] = (0, 0, 0, 0, 0, 0)
@ -1434,12 +1521,16 @@ class Star(ShapeBase):
self._batch = batch or Batch() self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group) 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, self._create_vertex_list()
colors=('Bn', self._rgba * self._num_verts),
rotation=('f', (rotation,) * self._num_verts),
translation=('f', (x, y) * self._num_verts))
self._update_vertices() 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): def _update_vertices(self):
if not self._visible: if not self._visible:
vertices = (0, 0) * self._num_spikes * 6 vertices = (0, 0) * self._num_spikes * 6
@ -1541,12 +1632,16 @@ class Polygon(ShapeBase):
self._batch = batch or Batch() self._batch = batch or Batch()
self._group = _ShapeGroup(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, program, group) 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, self._create_vertex_list()
colors=('Bn', self._rgba * self._num_verts),
translation=('f', (coordinates[0]) * self._num_verts))
self._update_vertices() self._update_vertices()
self._update_color() 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): def _update_vertices(self):
if not self._visible: if not self._visible:
self._vertex_list.vertices[:] = tuple([0] * ((len(self._coordinates) - 2) * 6)) self._vertex_list.vertices[:] = tuple([0] * ((len(self._coordinates) - 2) * 6))

View File

@ -217,7 +217,7 @@ class InlineElement:
""" """
return self._position 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. """Construct an instance of the element at the given coordinates.
Called when the element's position within a layout changes, either 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) descent = min(0, -anchor_y)
super().__init__(ascent, descent, self.width) super().__init__(ascent, descent, self.width)
def place(self, layout, x, y): def place(self, layout, x, y, z):
program = pyglet.text.layout.get_default_layout_shader() program = pyglet.text.layout.get_default_image_layout_shader()
group = _InlineElementGroup(self.image.get_texture(), program, 0, layout.group) group = _InlineElementGroup(self.image.get_texture(), program, 0, layout.group)
x1 = x x1 = x
y1 = y + self.descent y1 = y + self.descent
@ -96,7 +96,7 @@ class ImageElement(pyglet.text.document.InlineElement):
y2 = y + self.height + self.descent y2 = y + self.height + self.descent
vertex_list = program.vertex_list_indexed(4, pyglet.gl.GL_TRIANGLES, [0, 1, 2, 0, 2, 3], vertex_list = program.vertex_list_indexed(4, pyglet.gl.GL_TRIANGLES, [0, 1, 2, 0, 2, 3],
layout.batch, group, 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)) tex_coords=('f', self.image.tex_coords))
self.vertex_lists[layout] = vertex_list 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 decoration_vertex_source = """#version 330 core
in vec3 position; in vec3 position;
in vec4 colors; in vec4 colors;
@ -673,6 +698,17 @@ def get_default_layout_shader():
return pyglet.gl.current_context.pyglet_text_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(): def get_default_decoration_shader():
try: try:
return pyglet.gl.current_context.pyglet_text_decoration_shader return pyglet.gl.current_context.pyglet_text_decoration_shader