sync pyglet

This commit is contained in:
shenjack 2023-06-17 01:09:43 +08:00
parent c79d98f89e
commit 05c39fefd2
2 changed files with 173 additions and 9 deletions

View File

@ -19,12 +19,17 @@ by this package.
import os
import sys
import weakref
from typing import Dict, Union, BinaryIO, Optional, List, Iterable
import pyglet
from pyglet.font.user import UserDefinedFont
from pyglet import gl
if not getattr(sys, 'is_pyglet_doc_run', False):
def _get_system_font_class():
"""Get the appropriate class for the system being used.
Pyglet relies on OS dependent font systems for loading fonts and glyph creation.
"""
if pyglet.compat_platform == 'darwin':
from pyglet.font.quartz import QuartzFont
_font_class = QuartzFont
@ -37,18 +42,86 @@ if not getattr(sys, 'is_pyglet_doc_run', False):
else:
from pyglet.font.win32 import GDIPlusFont
_font_class = GDIPlusFont
else:
from pyglet.font.freetype import FreeTypeFont
_font_class = FreeTypeFont
return _font_class
def have_font(name):
def create_font(name: str, mappings: Dict[str, pyglet.image.ImageData], default_char: str,
ascent: Optional[float] = None,
descent: Optional[float] = None, size: Optional[float] = None, bold: bool = False,
italic: bool = False, stretch: bool = False, dpi: Optional[float] = None,
font_class=UserDefinedFont):
"""
Create a custom font with the mappings provided.
If a character in a string is not mapped in the font, it will use the default_char as fallback.
Default size is 12.
Ascent and descent will use the image dimensions as default, but can be provided.
The rest of the font parameters are used for font lookups.
"""
# Arbitrary default size
if size is None:
size = 12
if dpi is None:
dpi = 96
# Locate or create font cache
shared_object_space = gl.current_context.object_space
if not hasattr(shared_object_space, 'pyglet_font_font_cache'):
shared_object_space.pyglet_font_font_cache = weakref.WeakValueDictionary()
shared_object_space.pyglet_font_font_hold = []
shared_object_space.pyglet_font_font_name_match = {} # Match a tuple to specific name to reduce lookups.
font_cache = shared_object_space.pyglet_font_font_cache
font_hold = shared_object_space.pyglet_font_font_hold
# Look for font name in font cache
descriptor = (name, size, bold, italic, stretch, dpi)
if descriptor in font_cache:
raise Exception("A font with these parameters has already been created.", descriptor)
if _system_font_class.have_font(name):
raise Exception(f"Font name: '{name}' already exists within the system fonts.")
# Not in cache, create from scratch
font = font_class(mappings, default_char, name, ascent, descent, size, bold, italic, stretch, dpi)
# Save parameters for new-style layout classes to recover
# TODO: add properties to the Font classes, so these can be queried:
font.size = size
font.bold = bold
font.italic = italic
font.stretch = stretch
font.dpi = dpi
if name not in _user_fonts:
_user_fonts.append(name)
# Cache font in weak-ref dictionary to avoid reloading while still in use
font_cache[descriptor] = font
# Hold onto refs of last three loaded fonts to prevent them being
# collected if momentarily dropped.
del font_hold[3:]
font_hold.insert(0, font)
return font
def have_font(name: str) -> bool:
"""Check if specified system font name is available."""
return _font_class.have_font(name)
return name in _user_fonts or _system_font_class.have_font(name)
def load(name=None, size=None, bold=False, italic=False, stretch=False, dpi=None):
def load(name: Optional[Union[str, Iterable[str]]] = None, size: Optional[float] = None, bold: bool = False,
italic: bool = False,
stretch: bool = False, dpi: Optional[float] = None):
"""Load a font for rendering.
:Parameters:
@ -101,7 +174,7 @@ def load(name=None, size=None, bold=False, italic=False, stretch=False, dpi=None
# Find first matching name, cache it.
found_name = None
for n in name:
if _font_class.have_font(n):
if n in _user_fonts or _system_font_class.have_font(n):
found_name = n
break
@ -114,7 +187,7 @@ def load(name=None, size=None, bold=False, italic=False, stretch=False, dpi=None
return font_cache[descriptor]
# Not in cache, create from scratch
font = _font_class(name, size, bold=bold, italic=italic, stretch=stretch, dpi=dpi)
font = _system_font_class(name, size, bold=bold, italic=italic, stretch=stretch, dpi=dpi)
# Save parameters for new-style layout classes to recover
# TODO: add properties to the Font classes, so these can be queried:
@ -135,7 +208,12 @@ def load(name=None, size=None, bold=False, italic=False, stretch=False, dpi=None
return font
def add_file(font):
if not getattr(sys, 'is_pyglet_doc_run', False):
_system_font_class = _get_system_font_class()
_user_fonts = []
def add_file(font: Union[str, BinaryIO]):
"""Add a font to pyglet's search path.
In order to load a font that is not installed on the system, you must
@ -156,7 +234,7 @@ def add_file(font):
font = open(font, 'rb')
if hasattr(font, 'read'):
font = font.read()
_font_class.add_font_data(font)
_system_font_class.add_font_data(font)
def add_directory(directory):

86
libs/pyglet/font/user.py Normal file
View File

@ -0,0 +1,86 @@
from typing import Dict, Optional
import pyglet
from pyglet.font import base
class UserGlyphRenderer(base.GlyphRenderer):
def __init__(self, font: 'UserDefinedFont'):
self._font = font
self._font.glyphs[self._font.default_char] = self.render(self._font.default_char)
super().__init__(font)
def render(self, text: str):
image_data = self._font.mappings[text]
glyph = self._font.create_glyph(image_data)
glyph.set_bearings(-self._font.descent, 0, image_data.width)
return glyph
class UserDefinedFont(base.Font):
"""A basic UserDefinedFont, it takes a mappings of ImageData """
glyph_renderer_class = UserGlyphRenderer
def __init__(self, mappings: Dict[str, pyglet.image.ImageData], default_char: str, name: str, ascent: float,
descent: float, size: float, bold: bool = False, italic: bool = False, stretch: bool = False,
dpi: int = None,
locale: Optional[str] = None):
super().__init__()
self._name = name
self.mappings: Dict[str, pyglet.image.ImageData] = mappings
if default_char not in self.mappings:
raise Exception(f"Default character: '{default_char}' must exist within your mappings.")
if ascent is None or descent is None:
image = list(mappings.values())[0]
if ascent is None:
ascent = image.height
if descent is None:
descent = 0
self.ascent = ascent
self.descent = descent
self.default_char = default_char
self.bold = bold
self.italic = italic
self.stretch = stretch
self.dpi = dpi
self.size = size
self.locale = locale
@property
def name(self):
return self._name
def get_glyphs(self, text):
"""Create and return a list of Glyphs for `text`.
If any characters do not have a known glyph representation in this
font, a substitution will be made.
:Parameters:
`text` : str or unicode
Text to render.
:rtype: list of `Glyph`
"""
glyph_renderer = None
glyphs = [] # glyphs that are committed.
for c in base.get_grapheme_clusters(str(text)):
# Get the glyph for 'c'. Hide tabs (Windows and Linux render
# boxes)
if c == '\t':
c = ' '
if c not in self.glyphs:
if not glyph_renderer:
glyph_renderer = self.glyph_renderer_class(self)
if c not in self.mappings:
c = self.default_char
else:
self.glyphs[c] = glyph_renderer.render(c)
glyphs.append(self.glyphs[c])
return glyphs