shenjack
d84b490b99
Add | more formatter and some more Fix | type mis match sync pyglet Enhance | logger with Template add lib-not-dr as requirement sync pyglet sync pyglet Add | add lto=yes to nuitka_build just incase sync pyglet sync lib_not_dr Remove | external requirement lib-not-dr some logger sync lib-not-dr sync pyglet sync lib-not-dr sync lib-not-dr sync pyglet sync pyglet Fix | console thread been block Update DR rs and DR sdk sync lib not dr sync lib-not-dr sync lib-not-dr sync pyglet and lib-not-dr sync pyglet 0.1.8 sync lib not dr logger almost done? almost! sync pyglet (clicpboard support!) sync lib not dr sync lib not dr color code and sync pyglet do not show memory and progress building localy sync pyglet synclibs
615 lines
20 KiB
Python
615 lines
20 KiB
Python
# TODO Windows Vista: need to call SetProcessDPIAware? May affect GDI+ calls as well as font.
|
|
|
|
import math
|
|
import warnings
|
|
|
|
from sys import byteorder
|
|
from typing import Optional
|
|
|
|
import pyglet
|
|
from pyglet.font import base
|
|
from pyglet.font import win32query
|
|
import pyglet.image
|
|
from pyglet.libs.win32.constants import *
|
|
from pyglet.libs.win32.context_managers import device_context
|
|
from pyglet.libs.win32.types import *
|
|
from pyglet.libs.win32 import _gdi32 as gdi32, _user32 as user32
|
|
from pyglet.libs.win32 import _kernel32 as kernel32
|
|
from pyglet.util import asbytes
|
|
|
|
_debug_font = pyglet.options['debug_font']
|
|
|
|
|
|
def str_ucs2(text):
|
|
if byteorder == 'big':
|
|
text = text.encode('utf_16_be')
|
|
else:
|
|
text = text.encode('utf_16_le') # explicit endian avoids BOM
|
|
return create_string_buffer(text + '\0')
|
|
|
|
|
|
_debug_dir = 'debug_font'
|
|
|
|
|
|
def _debug_filename(base, extension):
|
|
import os
|
|
if not os.path.exists(_debug_dir):
|
|
os.makedirs(_debug_dir)
|
|
name = f'{os.path.join(_debug_dir, base)}-{{0}}.{{1}}'
|
|
num = 1
|
|
while os.path.exists(name.format(num, extension)):
|
|
num += 1
|
|
return name.format(num, extension)
|
|
|
|
|
|
def _debug_image(image, name):
|
|
filename = _debug_filename(name, 'png')
|
|
image.save(filename)
|
|
_debug(f'Saved image {image} to {filename}')
|
|
|
|
|
|
_debug_logfile = None
|
|
|
|
|
|
def _debug(msg):
|
|
global _debug_logfile
|
|
if not _debug_logfile:
|
|
_debug_logfile = open(_debug_filename('log', 'txt'), 'wt')
|
|
_debug_logfile.write(msg + '\n')
|
|
|
|
|
|
class Win32GlyphRenderer(base.GlyphRenderer):
|
|
|
|
def __init__(self, font):
|
|
self._bitmap = None
|
|
self._dc = None
|
|
self._bitmap_rect = None
|
|
super(Win32GlyphRenderer, self).__init__(font)
|
|
self.font = font
|
|
|
|
# Pessimistically round up width and height to 4 byte alignment
|
|
width = font.max_glyph_width
|
|
height = font.ascent - font.descent
|
|
width = (width | 0x3) + 1
|
|
height = (height | 0x3) + 1
|
|
self._create_bitmap(width, height)
|
|
|
|
gdi32.SelectObject(self._dc, self.font.hfont)
|
|
|
|
def _create_bitmap(self, width, height) -> None:
|
|
pass
|
|
|
|
def render(self, text: str) -> pyglet.font.base.Glyph:
|
|
raise NotImplementedError('abstract')
|
|
|
|
|
|
class GDIGlyphRenderer(Win32GlyphRenderer):
|
|
|
|
def __del__(self):
|
|
try:
|
|
if self._dc:
|
|
gdi32.DeleteDC(self._dc)
|
|
if self._bitmap:
|
|
gdi32.DeleteObject(self._bitmap)
|
|
except:
|
|
pass
|
|
|
|
def render(self, text: str) -> pyglet.font.base.Glyph:
|
|
# Attempt to get ABC widths (only for TrueType)
|
|
abc = ABC()
|
|
if gdi32.GetCharABCWidthsW(
|
|
self._dc,
|
|
ord(text), ord(text), byref(abc)
|
|
):
|
|
width = abc.abcB
|
|
lsb = abc.abcA
|
|
advance = abc.abcA + abc.abcB + abc.abcC
|
|
|
|
else:
|
|
width_buf = c_int()
|
|
gdi32.GetCharWidth32W(
|
|
self._dc, ord(text), ord(text), byref(width_buf))
|
|
|
|
width = width_buf.value
|
|
lsb = 0
|
|
advance = width
|
|
|
|
# Can't get glyph-specific dimensions, use whole line-height.
|
|
height = self._bitmap_height
|
|
image = self._get_image(text, width, height, lsb)
|
|
|
|
glyph = self.font.create_glyph(image)
|
|
glyph.set_bearings(-self.font.descent, lsb, advance)
|
|
|
|
if _debug_font:
|
|
_debug(f'{self}.render({text})')
|
|
_debug(f'abc.abcA = {abc.abcA}')
|
|
_debug(f'abc.abcB = {abc.abcB}')
|
|
_debug(f'abc.abcC = {abc.abcC}')
|
|
_debug(f'width = {width}')
|
|
_debug(f'height = {height}')
|
|
_debug(f'lsb = {lsb}')
|
|
_debug(f'advance = {advance}')
|
|
_debug_image(image, f'glyph_{text}')
|
|
_debug_image(self.font.textures[0], f'tex_{text}')
|
|
|
|
return glyph
|
|
|
|
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
|
|
# create an 8-bit palette bitmap with 256 shades of grey, but
|
|
# unfortunately antialiasing will not work on such a bitmap. So, we
|
|
# use a 32-bit bitmap and use the red channel as OpenGL's alpha.
|
|
|
|
gdi32.SelectObject(self._dc, self._bitmap)
|
|
gdi32.SelectObject(self._dc, self.font.hfont)
|
|
gdi32.SetBkColor(self._dc, 0x0)
|
|
gdi32.SetTextColor(self._dc, 0x00ffffff)
|
|
gdi32.SetBkMode(self._dc, OPAQUE)
|
|
|
|
# Draw to DC
|
|
user32.FillRect(self._dc, byref(self._bitmap_rect), self._black)
|
|
gdi32.ExtTextOutA(
|
|
self._dc, -lsb, 0, 0, None, text,
|
|
len(text), None)
|
|
|
|
gdi32.GdiFlush()
|
|
|
|
# Create glyph object and copy bitmap data to texture
|
|
image = pyglet.image.ImageData(
|
|
width, height,
|
|
'AXXX', self._bitmap_data, self._bitmap_rect.right * 4)
|
|
|
|
return image
|
|
|
|
def _create_bitmap(self, width: int, height: int) -> None:
|
|
self._black = gdi32.GetStockObject(BLACK_BRUSH)
|
|
self._white = gdi32.GetStockObject(WHITE_BRUSH)
|
|
|
|
if self._dc:
|
|
gdi32.ReleaseDC(self._dc)
|
|
if self._bitmap:
|
|
gdi32.DeleteObject(self._bitmap)
|
|
|
|
pitch = width * 4
|
|
data = POINTER(c_byte * (height * pitch))()
|
|
info = BITMAPINFO()
|
|
info.bmiHeader.biSize = sizeof(info.bmiHeader)
|
|
info.bmiHeader.biWidth = width
|
|
info.bmiHeader.biHeight = height
|
|
info.bmiHeader.biPlanes = 1
|
|
info.bmiHeader.biBitCount = 32
|
|
info.bmiHeader.biCompression = BI_RGB
|
|
|
|
self._dc = gdi32.CreateCompatibleDC(None)
|
|
self._bitmap = gdi32.CreateDIBSection(
|
|
None,
|
|
byref(info), DIB_RGB_COLORS, byref(data), None,
|
|
0)
|
|
# Spookiness: the above line causes a "not enough storage" error,
|
|
# even though that error cannot be generated according to docs,
|
|
# and everything works fine anyway. Call SetLastError to clear it.
|
|
kernel32.SetLastError(0)
|
|
|
|
self._bitmap_data = data.contents
|
|
self._bitmap_rect = RECT()
|
|
self._bitmap_rect.left = 0
|
|
self._bitmap_rect.right = width
|
|
self._bitmap_rect.top = 0
|
|
self._bitmap_rect.bottom = height
|
|
self._bitmap_height = height
|
|
|
|
if _debug_font:
|
|
_debug(f'{self}._create_dc({width}, {height})')
|
|
_debug(f'_dc = {self._dc}')
|
|
_debug(f'_bitmap = {self._bitmap}')
|
|
_debug(f'pitch = {pitch}')
|
|
_debug(f'info.bmiHeader.biSize = {info.bmiHeader.biSize}')
|
|
|
|
|
|
class Win32Font(base.Font):
|
|
|
|
glyph_renderer_class = GDIGlyphRenderer
|
|
|
|
def __init__(
|
|
self,
|
|
name: str, size: int,
|
|
bold: bool = False, italic: bool = False, stretch: bool = False,
|
|
dpi: Optional[float] = None
|
|
):
|
|
super(Win32Font, self).__init__()
|
|
|
|
self.logfont = self.get_logfont(name, size, bold, italic, dpi)
|
|
self.hfont = gdi32.CreateFontIndirectW(byref(self.logfont))
|
|
|
|
# Create a dummy DC for coordinate mapping
|
|
with device_context(None) as dc:
|
|
metrics = TEXTMETRIC()
|
|
gdi32.SelectObject(dc, self.hfont)
|
|
gdi32.GetTextMetricsA(dc, byref(metrics))
|
|
self.ascent = metrics.tmAscent
|
|
self.descent = -metrics.tmDescent
|
|
self.max_glyph_width = metrics.tmMaxCharWidth
|
|
|
|
def __del__(self):
|
|
gdi32.DeleteObject(self.hfont)
|
|
|
|
@staticmethod
|
|
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
|
|
with device_context(None) as dc:
|
|
|
|
# Default to 96 DPI unless otherwise specified
|
|
if dpi is None:
|
|
dpi = 96
|
|
log_pixels_y = dpi
|
|
|
|
# Create LOGFONTW font description struct
|
|
logfont = LOGFONTW()
|
|
|
|
# Convert point size to actual device pixels
|
|
logfont.lfHeight = int(-size * log_pixels_y // 72)
|
|
|
|
# Configure the LOGFONTW's font properties
|
|
if bold:
|
|
logfont.lfWeight = FW_BOLD
|
|
else:
|
|
logfont.lfWeight = FW_NORMAL
|
|
logfont.lfItalic = italic
|
|
logfont.lfFaceName = name
|
|
logfont.lfQuality = ANTIALIASED_QUALITY
|
|
|
|
return logfont
|
|
|
|
@classmethod
|
|
def have_font(cls, name) -> bool:
|
|
# [ ] add support for loading raster fonts
|
|
return win32query.have_font(name)
|
|
|
|
@classmethod
|
|
def add_font_data(cls, data):
|
|
numfonts = c_uint32()
|
|
_handle = gdi32.AddFontMemResourceEx(data, len(data), 0, byref(numfonts))
|
|
|
|
# None means a null handle was returned, ie something went wrong
|
|
if _handle is None:
|
|
raise ctypes.WinError()
|
|
|
|
|
|
|
|
# --- GDI+ font rendering ---
|
|
from pyglet.image.codecs.gdiplus import PixelFormat32bppARGB, gdiplus, Rect
|
|
from pyglet.image.codecs.gdiplus import ImageLockModeRead, BitmapData
|
|
|
|
DriverStringOptionsCmapLookup = 1
|
|
DriverStringOptionsRealizedAdvance = 4
|
|
TextRenderingHintAntiAlias = 4
|
|
TextRenderingHintAntiAliasGridFit = 3
|
|
|
|
StringFormatFlagsDirectionRightToLeft = 0x00000001
|
|
StringFormatFlagsDirectionVertical = 0x00000002
|
|
StringFormatFlagsNoFitBlackBox = 0x00000004
|
|
StringFormatFlagsDisplayFormatControl = 0x00000020
|
|
StringFormatFlagsNoFontFallback = 0x00000400
|
|
StringFormatFlagsMeasureTrailingSpaces = 0x00000800
|
|
StringFormatFlagsNoWrap = 0x00001000
|
|
StringFormatFlagsLineLimit = 0x00002000
|
|
StringFormatFlagsNoClip = 0x00004000
|
|
|
|
|
|
class Rectf(ctypes.Structure):
|
|
_fields_ = [
|
|
('x', ctypes.c_float),
|
|
('y', ctypes.c_float),
|
|
('width', ctypes.c_float),
|
|
('height', ctypes.c_float),
|
|
]
|
|
|
|
|
|
class GDIPlusGlyphRenderer(Win32GlyphRenderer):
|
|
def __del__(self):
|
|
try:
|
|
if self._matrix:
|
|
res = gdiplus.GdipDeleteMatrix(self._matrix)
|
|
if self._brush:
|
|
res = gdiplus.GdipDeleteBrush(self._brush)
|
|
if self._graphics:
|
|
res = gdiplus.GdipDeleteGraphics(self._graphics)
|
|
if self._bitmap:
|
|
res = gdiplus.GdipDisposeImage(self._bitmap)
|
|
if self._dc:
|
|
res = user32.ReleaseDC(0, self._dc)
|
|
except:
|
|
pass
|
|
|
|
def _create_bitmap(self, width, height):
|
|
self._data = (BYTE * (4 * width * height))()
|
|
self._bitmap = ctypes.c_void_p()
|
|
self._format = PixelFormat32bppARGB
|
|
gdiplus.GdipCreateBitmapFromScan0(width, height, width * 4,
|
|
self._format, self._data, ctypes.byref(self._bitmap))
|
|
|
|
self._graphics = ctypes.c_void_p()
|
|
gdiplus.GdipGetImageGraphicsContext(self._bitmap,
|
|
ctypes.byref(self._graphics))
|
|
gdiplus.GdipSetPageUnit(self._graphics, UnitPixel)
|
|
|
|
self._dc = user32.GetDC(0)
|
|
gdi32.SelectObject(self._dc, self.font.hfont)
|
|
|
|
gdiplus.GdipSetTextRenderingHint(self._graphics,
|
|
TextRenderingHintAntiAliasGridFit)
|
|
|
|
self._brush = ctypes.c_void_p()
|
|
gdiplus.GdipCreateSolidFill(0xffffffff, ctypes.byref(self._brush))
|
|
|
|
self._matrix = ctypes.c_void_p()
|
|
gdiplus.GdipCreateMatrix(ctypes.byref(self._matrix))
|
|
|
|
self._flags = (DriverStringOptionsCmapLookup |
|
|
DriverStringOptionsRealizedAdvance)
|
|
|
|
self._rect = Rect(0, 0, width, height)
|
|
|
|
self._bitmap_height = height
|
|
|
|
def render(self, text):
|
|
ch = ctypes.create_unicode_buffer(text)
|
|
len_ch = len(text)
|
|
|
|
# Layout rectangle; not clipped against so not terribly important.
|
|
width = 10000
|
|
height = self._bitmap_height
|
|
rect = Rectf(0, self._bitmap_height
|
|
- self.font.ascent + self.font.descent,
|
|
width, height)
|
|
|
|
# Set up GenericTypographic with 1 character measure range
|
|
generic = ctypes.c_void_p()
|
|
gdiplus.GdipStringFormatGetGenericTypographic(ctypes.byref(generic))
|
|
fmt = ctypes.c_void_p()
|
|
gdiplus.GdipCloneStringFormat(generic, ctypes.byref(fmt))
|
|
gdiplus.GdipDeleteStringFormat(generic)
|
|
|
|
# --- Measure advance
|
|
|
|
# XXX HACK HACK HACK
|
|
# Windows GDI+ is a filthy broken toy. No way to measure the bounding
|
|
# box of a string, or to obtain LSB. What a joke.
|
|
#
|
|
# For historical note, GDI cannot be used because it cannot composite
|
|
# into a bitmap with alpha.
|
|
#
|
|
# It looks like MS have abandoned GDI and GDI+ and are finally
|
|
# supporting accurate text measurement with alpha composition in .NET
|
|
# 2.0 (WinForms) via the TextRenderer class; this has no C interface
|
|
# though, so we're entirely screwed.
|
|
#
|
|
# So anyway, we first try to get the width with GdipMeasureString.
|
|
# Then if it's a TrueType font, we use GetCharABCWidthsW to get the
|
|
# correct LSB. If it's a negative LSB, we move the layoutRect `rect`
|
|
# to the right so that the whole glyph is rendered on the surface.
|
|
# For positive LSB, we let the renderer render the correct white
|
|
# space and we don't pass the LSB info to the Glyph.set_bearings
|
|
|
|
bbox = Rectf()
|
|
flags = (StringFormatFlagsMeasureTrailingSpaces |
|
|
StringFormatFlagsNoClip |
|
|
StringFormatFlagsNoFitBlackBox)
|
|
gdiplus.GdipSetStringFormatFlags(fmt, flags)
|
|
gdiplus.GdipMeasureString(self._graphics,
|
|
ch,
|
|
len_ch,
|
|
self.font._gdipfont,
|
|
ctypes.byref(rect),
|
|
fmt,
|
|
ctypes.byref(bbox),
|
|
None,
|
|
None)
|
|
|
|
# We only care about the advance from this whole thing.
|
|
advance = int(math.ceil(bbox.width))
|
|
|
|
# GDI functions only work for a single character so we transform
|
|
# grapheme \r\n into \r
|
|
if text == '\r\n':
|
|
text = '\r'
|
|
|
|
# XXX END HACK HACK HACK
|
|
|
|
abc = ABC()
|
|
width = 0
|
|
lsb = 0
|
|
ttf_font = True
|
|
# Use GDI to get code points for the text passed. This is almost always 1.
|
|
# For special unicode characters it may be comprised of 2+ codepoints. Get the width/lsb of each.
|
|
# Function only works on TTF fonts.
|
|
for codepoint in [ord(c) for c in text]:
|
|
if gdi32.GetCharABCWidthsW(self._dc, codepoint, codepoint, byref(abc)):
|
|
lsb += abc.abcA
|
|
width += abc.abcB
|
|
|
|
if lsb < 0:
|
|
# Negative LSB: we shift the layout rect to the right
|
|
# Otherwise we will cut the left part of the glyph
|
|
rect.x = -lsb
|
|
width -= lsb
|
|
else:
|
|
width += lsb
|
|
else:
|
|
ttf_font = False
|
|
break
|
|
|
|
# Almost always a TTF font. Haven't seen a modern font that GetCharABCWidthsW fails on.
|
|
# For safety, just use the advance as the width.
|
|
if not ttf_font:
|
|
width = advance
|
|
|
|
# This hack bumps up the width if the font is italic;
|
|
# this compensates for some common fonts. It's also a stupid
|
|
# waste of texture memory.
|
|
if self.font.italic:
|
|
width += width // 2
|
|
# Do not enlarge more than the _rect width.
|
|
width = min(width, self._rect.Width)
|
|
|
|
# Draw character to bitmap
|
|
gdiplus.GdipGraphicsClear(self._graphics, 0x00000000)
|
|
gdiplus.GdipDrawString(self._graphics,
|
|
ch,
|
|
len_ch,
|
|
self.font._gdipfont,
|
|
ctypes.byref(rect),
|
|
fmt,
|
|
self._brush)
|
|
|
|
gdiplus.GdipFlush(self._graphics, 1)
|
|
gdiplus.GdipDeleteStringFormat(fmt)
|
|
|
|
bitmap_data = BitmapData()
|
|
gdiplus.GdipBitmapLockBits(
|
|
self._bitmap,
|
|
byref(self._rect), ImageLockModeRead, self._format,
|
|
byref(bitmap_data))
|
|
|
|
# Create buffer for RawImage
|
|
buffer = create_string_buffer(
|
|
bitmap_data.Stride * bitmap_data.Height)
|
|
memmove(buffer, bitmap_data.Scan0, len(buffer))
|
|
|
|
# Unlock data
|
|
gdiplus.GdipBitmapUnlockBits(self._bitmap, byref(bitmap_data))
|
|
|
|
image = pyglet.image.ImageData(
|
|
width, height,
|
|
'BGRA', buffer, -bitmap_data.Stride)
|
|
|
|
glyph = self.font.create_glyph(image)
|
|
# Only pass negative LSB info
|
|
lsb = min(lsb, 0)
|
|
glyph.set_bearings(-self.font.descent, lsb, advance)
|
|
return glyph
|
|
|
|
|
|
FontStyleBold = 1
|
|
FontStyleItalic = 2
|
|
UnitPixel = 2
|
|
UnitPoint = 3
|
|
|
|
|
|
class GDIPlusFont(Win32Font):
|
|
glyph_renderer_class = GDIPlusGlyphRenderer
|
|
|
|
_private_fonts = None
|
|
|
|
_default_name = 'Arial'
|
|
|
|
def __init__(self, name, size, bold=False, italic=False, stretch=False, dpi=None):
|
|
if not name:
|
|
name = self._default_name
|
|
|
|
# assert type(bold) is bool, "Only a boolean value is supported for bold in the current font renderer."
|
|
# assert type(italic) is bool, "Only a boolean value is supported for bold in the current font renderer."
|
|
|
|
if stretch:
|
|
warnings.warn("The current font render does not support stretching.")
|
|
|
|
super().__init__(name, size, bold, italic, stretch, dpi)
|
|
|
|
self._name = name
|
|
|
|
family = ctypes.c_void_p()
|
|
|
|
# GDI will add @ in front of a localized font for some Asian languages. However, GDI will also not find it
|
|
# based on that name (???). Here we remove it before checking font collections.
|
|
if name[0] == "@":
|
|
name = name[1:]
|
|
|
|
name = ctypes.c_wchar_p(name)
|
|
|
|
# Look in private collection first:
|
|
if self._private_fonts:
|
|
gdiplus.GdipCreateFontFamilyFromName(name, self._private_fonts, ctypes.byref(family))
|
|
|
|
# Then in system collection:
|
|
if not family:
|
|
if _debug_font:
|
|
print(f"Warning: Font '{name}' was not found. Defaulting to: {self._default_name}")
|
|
|
|
gdiplus.GdipCreateFontFamilyFromName(name, None, ctypes.byref(family))
|
|
|
|
# Nothing found, use default font.
|
|
if not family:
|
|
self._name = self._default_name
|
|
gdiplus.GdipCreateFontFamilyFromName(ctypes.c_wchar_p(self._name), None, ctypes.byref(family))
|
|
|
|
if dpi is None:
|
|
unit = UnitPoint
|
|
self.dpi = 96
|
|
else:
|
|
unit = UnitPixel
|
|
size = (size * dpi) // 72
|
|
self.dpi = dpi
|
|
|
|
style = 0
|
|
if bold:
|
|
style |= FontStyleBold
|
|
if italic:
|
|
style |= FontStyleItalic
|
|
self._gdipfont = ctypes.c_void_p()
|
|
gdiplus.GdipCreateFont(family, ctypes.c_float(size), style, unit, ctypes.byref(self._gdipfont))
|
|
gdiplus.GdipDeleteFontFamily(family)
|
|
|
|
@property
|
|
def name(self):
|
|
return self._name
|
|
|
|
def __del__(self):
|
|
super(GDIPlusFont, self).__del__()
|
|
gdiplus.GdipDeleteFont(self._gdipfont)
|
|
|
|
@classmethod
|
|
def add_font_data(cls, data):
|
|
super(GDIPlusFont, cls).add_font_data(data)
|
|
|
|
if not cls._private_fonts:
|
|
cls._private_fonts = ctypes.c_void_p()
|
|
gdiplus.GdipNewPrivateFontCollection(
|
|
ctypes.byref(cls._private_fonts))
|
|
gdiplus.GdipPrivateAddMemoryFont(cls._private_fonts, data, len(data))
|
|
|
|
@classmethod
|
|
def have_font(cls, name):
|
|
family = ctypes.c_void_p()
|
|
|
|
# Look in private collection first:
|
|
num_count = ctypes.c_int()
|
|
gdiplus.GdipGetFontCollectionFamilyCount(
|
|
cls._private_fonts, ctypes.byref(num_count))
|
|
gpfamilies = (ctypes.c_void_p * num_count.value)()
|
|
numFound = ctypes.c_int()
|
|
gdiplus.GdipGetFontCollectionFamilyList(
|
|
cls._private_fonts, num_count, gpfamilies, ctypes.byref(numFound))
|
|
|
|
font_name = ctypes.create_unicode_buffer(32)
|
|
for gpfamily in gpfamilies:
|
|
gdiplus.GdipGetFamilyName(gpfamily, font_name, '\0')
|
|
if font_name.value == name:
|
|
return True
|
|
|
|
# Else call parent class for system fonts
|
|
return super(GDIPlusFont, cls).have_font(name)
|