sync pyglet update

This commit is contained in:
shenjack 2023-07-05 19:44:30 +08:00
parent bb83fbd117
commit f87ae43a10
2 changed files with 76 additions and 24 deletions

View File

@ -4,6 +4,8 @@ import math
import warnings import warnings
from sys import byteorder from sys import byteorder
from typing import Optional
import pyglet import pyglet
from pyglet.font import base from pyglet.font import base
from pyglet.font import win32query from pyglet.font import win32query
@ -25,7 +27,10 @@ def str_ucs2(text):
text = text.encode('utf_16_le') # explicit endian avoids BOM text = text.encode('utf_16_le') # explicit endian avoids BOM
return create_string_buffer(text + '\0') return create_string_buffer(text + '\0')
_debug_dir = 'debug_font' _debug_dir = 'debug_font'
def _debug_filename(base, extension): def _debug_filename(base, extension):
import os import os
if not os.path.exists(_debug_dir): if not os.path.exists(_debug_dir):
@ -36,20 +41,24 @@ def _debug_filename(base, extension):
num += 1 num += 1
return name.format(num, extension) return name.format(num, extension)
def _debug_image(image, name): def _debug_image(image, name):
filename = _debug_filename(name, 'png') filename = _debug_filename(name, 'png')
image.save(filename) image.save(filename)
_debug(f'Saved image {image} to {filename}') _debug(f'Saved image {image} to {filename}')
_debug_logfile = None _debug_logfile = None
def _debug(msg): def _debug(msg):
global _debug_logfile global _debug_logfile
if not _debug_logfile: if not _debug_logfile:
_debug_logfile = open(_debug_filename('log', 'txt'), 'wt') _debug_logfile = open(_debug_filename('log', 'txt'), 'wt')
_debug_logfile.write(msg + '\n') _debug_logfile.write(msg + '\n')
class Win32GlyphRenderer(base.GlyphRenderer):
class Win32GlyphRenderer(base.GlyphRenderer):
def __init__(self, font): def __init__(self, font):
self._bitmap = None self._bitmap = None
@ -67,13 +76,15 @@ class Win32GlyphRenderer(base.GlyphRenderer):
gdi32.SelectObject(self._dc, self.font.hfont) gdi32.SelectObject(self._dc, self.font.hfont)
def _create_bitmap(self, width, height): def _create_bitmap(self, width, height) -> None:
pass pass
def render(self, text): def render(self, text: str) -> pyglet.font.base.Glyph:
raise NotImplementedError('abstract') raise NotImplementedError('abstract')
class GDIGlyphRenderer(Win32GlyphRenderer): class GDIGlyphRenderer(Win32GlyphRenderer):
def __del__(self): def __del__(self):
try: try:
if self._dc: if self._dc:
@ -83,18 +94,22 @@ class GDIGlyphRenderer(Win32GlyphRenderer):
except: except:
pass pass
def render(self, text): def render(self, text: str) -> pyglet.font.base.Glyph:
# Attempt to get ABC widths (only for TrueType) # Attempt to get ABC widths (only for TrueType)
abc = ABC() abc = ABC()
if gdi32.GetCharABCWidthsW(self._dc, if gdi32.GetCharABCWidthsW(
ord(text), ord(text), byref(abc)): self._dc,
ord(text), ord(text), byref(abc)
):
width = abc.abcB width = abc.abcB
lsb = abc.abcA lsb = abc.abcA
advance = abc.abcA + abc.abcB + abc.abcC advance = abc.abcA + abc.abcB + abc.abcC
else: else:
width_buf = c_int() width_buf = c_int()
gdi32.GetCharWidth32W(self._dc, gdi32.GetCharWidth32W(
ord(text), ord(text), byref(width_buf)) self._dc, ord(text), ord(text), byref(width_buf))
width = width_buf.value width = width_buf.value
lsb = 0 lsb = 0
advance = width advance = width
@ -120,7 +135,7 @@ class GDIGlyphRenderer(Win32GlyphRenderer):
return glyph return glyph
def _get_image(self, text, width, height, lsb): def _get_image(self, text: str, width: int, height: int, lsb: int) -> pyglet.image.ImageData:
# There's no such thing as a greyscale bitmap format in GDI. We can # There's no such thing as a greyscale bitmap format in GDI. We can
# create an 8-bit palette bitmap with 256 shades of grey, but # create an 8-bit palette bitmap with 256 shades of grey, but
# unfortunately antialiasing will not work on such a bitmap. So, we # unfortunately antialiasing will not work on such a bitmap. So, we
@ -134,16 +149,20 @@ class GDIGlyphRenderer(Win32GlyphRenderer):
# Draw to DC # Draw to DC
user32.FillRect(self._dc, byref(self._bitmap_rect), self._black) user32.FillRect(self._dc, byref(self._bitmap_rect), self._black)
gdi32.ExtTextOutA(self._dc, -lsb, 0, 0, None, text, gdi32.ExtTextOutA(
self._dc, -lsb, 0, 0, None, text,
len(text), None) len(text), None)
gdi32.GdiFlush() gdi32.GdiFlush()
# Create glyph object and copy bitmap data to texture # Create glyph object and copy bitmap data to texture
image = pyglet.image.ImageData(width, height, image = pyglet.image.ImageData(
width, height,
'AXXX', self._bitmap_data, self._bitmap_rect.right * 4) 'AXXX', self._bitmap_data, self._bitmap_rect.right * 4)
return image return image
def _create_bitmap(self, width, height): def _create_bitmap(self, width: int, height: int) -> None:
self._black = gdi32.GetStockObject(BLACK_BRUSH) self._black = gdi32.GetStockObject(BLACK_BRUSH)
self._white = gdi32.GetStockObject(WHITE_BRUSH) self._white = gdi32.GetStockObject(WHITE_BRUSH)
@ -163,7 +182,8 @@ class GDIGlyphRenderer(Win32GlyphRenderer):
info.bmiHeader.biCompression = BI_RGB info.bmiHeader.biCompression = BI_RGB
self._dc = gdi32.CreateCompatibleDC(None) self._dc = gdi32.CreateCompatibleDC(None)
self._bitmap = gdi32.CreateDIBSection(None, self._bitmap = gdi32.CreateDIBSection(
None,
byref(info), DIB_RGB_COLORS, byref(data), None, byref(info), DIB_RGB_COLORS, byref(data), None,
0) 0)
# Spookiness: the above line causes a "not enough storage" error, # Spookiness: the above line causes a "not enough storage" error,
@ -186,10 +206,17 @@ class GDIGlyphRenderer(Win32GlyphRenderer):
_debug(f'pitch = {pitch}') _debug(f'pitch = {pitch}')
_debug(f'info.bmiHeader.biSize = {info.bmiHeader.biSize}') _debug(f'info.bmiHeader.biSize = {info.bmiHeader.biSize}')
class Win32Font(base.Font): class Win32Font(base.Font):
glyph_renderer_class = GDIGlyphRenderer glyph_renderer_class = GDIGlyphRenderer
def __init__(self, name, size, bold=False, italic=False, stretch=False, dpi=None): def __init__(
self,
name: str, size: int,
bold: bool = False, italic: bool = False, stretch: bool = False,
dpi: Optional[float] = None
):
super(Win32Font, self).__init__() super(Win32Font, self).__init__()
self.logfont = self.get_logfont(name, size, bold, italic, dpi) self.logfont = self.get_logfont(name, size, bold, italic, dpi)
@ -208,16 +235,35 @@ class Win32Font(base.Font):
gdi32.DeleteObject(self.hfont) gdi32.DeleteObject(self.hfont)
@staticmethod @staticmethod
def get_logfont(name, size, bold, italic, dpi): def get_logfont(name: str, size: float, bold: bool, italic: bool, dpi: Optional[float] = None) -> LOGFONTW:
"""Get a raw Win32 :py:class:`.LOGFONTW` struct for the given arguments.
Args:
name: The name of the font
size: The font size in points
bold: Whether to request the font as bold
italic: Whether to request the font as italic
dpi: The screen dpi
Returns:
LOGFONTW: a ctypes binding of a Win32 LOGFONTW struct
"""
# Create a dummy DC for coordinate mapping # Create a dummy DC for coordinate mapping
with device_context(None) as dc: with device_context(None) as dc:
# Default to 96 DPI unless otherwise specified
if dpi is None: if dpi is None:
dpi = 96 dpi = 96
logpixelsy = dpi log_pixels_y = dpi
# Create LOGFONTW font description struct
logfont = LOGFONTW() logfont = LOGFONTW()
# Conversion of point size to device pixels
logfont.lfHeight = int(-size * logpixelsy // 72) # Convert point size to actual device pixels
logfont.lfHeight = int(-size * log_pixels_y // 72)
# Configure the LOGFONTW's font properties
if bold: if bold:
logfont.lfWeight = FW_BOLD logfont.lfWeight = FW_BOLD
else: else:
@ -229,7 +275,7 @@ class Win32Font(base.Font):
return logfont return logfont
@classmethod @classmethod
def have_font(cls, name): def have_font(cls, name) -> bool:
# [ ] add support for loading raster fonts # [ ] add support for loading raster fonts
return win32query.have_font(name) return win32query.have_font(name)
@ -243,8 +289,8 @@ class Win32Font(base.Font):
raise ctypes.WinError() raise ctypes.WinError()
# --- GDI+ font rendering ---
# --- GDI+ font rendering ---
from pyglet.image.codecs.gdiplus import PixelFormat32bppARGB, gdiplus, Rect from pyglet.image.codecs.gdiplus import PixelFormat32bppARGB, gdiplus, Rect
from pyglet.image.codecs.gdiplus import ImageLockModeRead, BitmapData from pyglet.image.codecs.gdiplus import ImageLockModeRead, BitmapData
@ -263,6 +309,7 @@ StringFormatFlagsNoWrap = 0x00001000
StringFormatFlagsLineLimit = 0x00002000 StringFormatFlagsLineLimit = 0x00002000
StringFormatFlagsNoClip = 0x00004000 StringFormatFlagsNoClip = 0x00004000
class Rectf(ctypes.Structure): class Rectf(ctypes.Structure):
_fields_ = [ _fields_ = [
('x', ctypes.c_float), ('x', ctypes.c_float),
@ -271,6 +318,7 @@ class Rectf(ctypes.Structure):
('height', ctypes.c_float), ('height', ctypes.c_float),
] ]
class GDIPlusGlyphRenderer(Win32GlyphRenderer): class GDIPlusGlyphRenderer(Win32GlyphRenderer):
def __del__(self): def __del__(self):
try: try:
@ -305,11 +353,9 @@ class GDIPlusGlyphRenderer(Win32GlyphRenderer):
gdiplus.GdipSetTextRenderingHint(self._graphics, gdiplus.GdipSetTextRenderingHint(self._graphics,
TextRenderingHintAntiAliasGridFit) TextRenderingHintAntiAliasGridFit)
self._brush = ctypes.c_void_p() self._brush = ctypes.c_void_p()
gdiplus.GdipCreateSolidFill(0xffffffff, ctypes.byref(self._brush)) gdiplus.GdipCreateSolidFill(0xffffffff, ctypes.byref(self._brush))
self._matrix = ctypes.c_void_p() self._matrix = ctypes.c_void_p()
gdiplus.GdipCreateMatrix(ctypes.byref(self._matrix)) gdiplus.GdipCreateMatrix(ctypes.byref(self._matrix))
@ -429,11 +475,13 @@ class GDIPlusGlyphRenderer(Win32GlyphRenderer):
ctypes.byref(rect), ctypes.byref(rect),
fmt, fmt,
self._brush) self._brush)
gdiplus.GdipFlush(self._graphics, 1) gdiplus.GdipFlush(self._graphics, 1)
gdiplus.GdipDeleteStringFormat(fmt) gdiplus.GdipDeleteStringFormat(fmt)
bitmap_data = BitmapData() bitmap_data = BitmapData()
gdiplus.GdipBitmapLockBits(self._bitmap, gdiplus.GdipBitmapLockBits(
self._bitmap,
byref(self._rect), ImageLockModeRead, self._format, byref(self._rect), ImageLockModeRead, self._format,
byref(bitmap_data)) byref(bitmap_data))
@ -445,7 +493,8 @@ class GDIPlusGlyphRenderer(Win32GlyphRenderer):
# Unlock data # Unlock data
gdiplus.GdipBitmapUnlockBits(self._bitmap, byref(bitmap_data)) gdiplus.GdipBitmapUnlockBits(self._bitmap, byref(bitmap_data))
image = pyglet.image.ImageData(width, height, image = pyglet.image.ImageData(
width, height,
'BGRA', buffer, -bitmap_data.Stride) 'BGRA', buffer, -bitmap_data.Stride)
glyph = self.font.create_glyph(image) glyph = self.font.create_glyph(image)
@ -454,11 +503,13 @@ class GDIPlusGlyphRenderer(Win32GlyphRenderer):
glyph.set_bearings(-self.font.descent, lsb, advance) glyph.set_bearings(-self.font.descent, lsb, advance)
return glyph return glyph
FontStyleBold = 1 FontStyleBold = 1
FontStyleItalic = 2 FontStyleItalic = 2
UnitPixel = 2 UnitPixel = 2
UnitPoint = 3 UnitPoint = 3
class GDIPlusFont(Win32Font): class GDIPlusFont(Win32Font):
glyph_renderer_class = GDIPlusGlyphRenderer glyph_renderer_class = GDIPlusGlyphRenderer

View File

@ -232,6 +232,7 @@ class LOGFONT(Structure):
class LOGFONTW(Structure): class LOGFONTW(Structure):
# https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-logfontw
_fields_ = [ _fields_ = [
('lfHeight', LONG), ('lfHeight', LONG),
('lfWidth', LONG), ('lfWidth', LONG),