Spaces:
Runtime error
Runtime error
File size: 11,350 Bytes
d82cf6a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# TODO Tiger and later: need to set kWindowApplicationScaledAttribute for DPI independence?
import math
import warnings
from ctypes import c_void_p, c_int32, byref, c_byte
from pyglet.font import base
import pyglet.image
from pyglet.libs.darwin import cocoapy
cf = cocoapy.cf
ct = cocoapy.ct
quartz = cocoapy.quartz
class QuartzGlyphRenderer(base.GlyphRenderer):
def __init__(self, font):
super().__init__(font)
self.font = font
def render(self, text):
# Using CTLineDraw seems to be the only way to make sure that the text
# is drawn with the specified font when that font is a graphics font loaded from
# memory. For whatever reason, [NSAttributedString drawAtPoint:] ignores
# the graphics font if it not registered with the font manager.
# So we just use CTLineDraw for both graphics fonts and installed fonts.
ctFont = self.font.ctFont
# Create an attributed string using text and font.
attributes = c_void_p(cf.CFDictionaryCreateMutable(None, 1, cf.kCFTypeDictionaryKeyCallBacks, cf.kCFTypeDictionaryValueCallBacks))
cf.CFDictionaryAddValue(attributes, cocoapy.kCTFontAttributeName, ctFont)
string = c_void_p(cf.CFAttributedStringCreate(None, cocoapy.CFSTR(text), attributes))
# Create a CTLine object to render the string.
line = c_void_p(ct.CTLineCreateWithAttributedString(string))
cf.CFRelease(string)
cf.CFRelease(attributes)
# Get a bounding rectangle for glyphs in string.
count = len(text)
chars = (cocoapy.UniChar * count)(*list(map(ord,str(text))))
glyphs = (cocoapy.CGGlyph * count)()
ct.CTFontGetGlyphsForCharacters(ctFont, chars, glyphs, count)
rect = ct.CTFontGetBoundingRectsForGlyphs(ctFont, 0, glyphs, None, count)
# Get advance for all glyphs in string.
advance = ct.CTFontGetAdvancesForGlyphs(ctFont, 0, glyphs, None, count)
# Set image parameters:
# We add 2 pixels to the bitmap width and height so that there will be a 1-pixel border
# around the glyph image when it is placed in the texture atlas. This prevents
# weird artifacts from showing up around the edges of the rendered glyph textures.
# We adjust the baseline and lsb of the glyph by 1 pixel accordingly.
width = max(int(math.ceil(rect.size.width) + 2), 1)
height = max(int(math.ceil(rect.size.height) + 2), 1)
baseline = -int(math.floor(rect.origin.y)) + 1
lsb = int(math.floor(rect.origin.x)) - 1
advance = int(round(advance))
# Create bitmap context.
bitsPerComponent = 8
bytesPerRow = 4*width
colorSpace = c_void_p(quartz.CGColorSpaceCreateDeviceRGB())
bitmap = c_void_p(quartz.CGBitmapContextCreate(
None,
width,
height,
bitsPerComponent,
bytesPerRow,
colorSpace,
cocoapy.kCGImageAlphaPremultipliedLast))
# Draw text to bitmap context.
quartz.CGContextSetShouldAntialias(bitmap, True)
quartz.CGContextSetTextPosition(bitmap, -lsb, baseline)
ct.CTLineDraw(line, bitmap)
cf.CFRelease(line)
# Create an image to get the data out.
imageRef = c_void_p(quartz.CGBitmapContextCreateImage(bitmap))
bytesPerRow = quartz.CGImageGetBytesPerRow(imageRef)
dataProvider = c_void_p(quartz.CGImageGetDataProvider(imageRef))
imageData = c_void_p(quartz.CGDataProviderCopyData(dataProvider))
buffersize = cf.CFDataGetLength(imageData)
buffer = (c_byte * buffersize)()
byteRange = cocoapy.CFRange(0, buffersize)
cf.CFDataGetBytes(imageData, byteRange, buffer)
quartz.CGImageRelease(imageRef)
quartz.CGDataProviderRelease(imageData)
cf.CFRelease(bitmap)
cf.CFRelease(colorSpace)
glyph_image = pyglet.image.ImageData(width, height, 'RGBA', buffer, bytesPerRow)
glyph = self.font.create_glyph(glyph_image)
glyph.set_bearings(baseline, lsb, advance)
t = list(glyph.tex_coords)
glyph.tex_coords = t[9:12] + t[6:9] + t[3:6] + t[:3]
return glyph
class QuartzFont(base.Font):
glyph_renderer_class = QuartzGlyphRenderer
_loaded_CGFont_table = {}
def _lookup_font_with_family_and_traits(self, family, traits):
# This method searches the _loaded_CGFont_table to find a loaded
# font of the given family with the desired traits. If it can't find
# anything with the exact traits, it tries to fall back to whatever
# we have loaded that's close. If it can't find anything in the
# given family at all, it returns None.
# Check if we've loaded the font with the specified family.
if family not in self._loaded_CGFont_table:
return None
# Grab a dictionary of all fonts in the family, keyed by traits.
fonts = self._loaded_CGFont_table[family]
if not fonts:
return None
# Return font with desired traits if it is available.
if traits in fonts:
return fonts[traits]
# Otherwise try to find a font with some of the traits.
for (t, f) in fonts.items():
if traits & t:
return f
# Otherwise try to return a regular font.
if 0 in fonts:
return fonts[0]
# Otherwise return whatever we have.
return list(fonts.values())[0]
def _create_font_descriptor(self, family_name, traits):
# Create an attribute dictionary.
attributes = c_void_p(cf.CFDictionaryCreateMutable(None, 0, cf.kCFTypeDictionaryKeyCallBacks, cf.kCFTypeDictionaryValueCallBacks))
# Add family name to attributes.
cfname = cocoapy.CFSTR(family_name)
cf.CFDictionaryAddValue(attributes, cocoapy.kCTFontFamilyNameAttribute, cfname)
cf.CFRelease(cfname)
# Construct a CFNumber to represent the traits.
itraits = c_int32(traits)
symTraits = c_void_p(cf.CFNumberCreate(None, cocoapy.kCFNumberSInt32Type, byref(itraits)))
if symTraits:
# Construct a dictionary to hold the traits values.
traitsDict = c_void_p(cf.CFDictionaryCreateMutable(None, 0, cf.kCFTypeDictionaryKeyCallBacks, cf.kCFTypeDictionaryValueCallBacks))
if traitsDict:
# Add CFNumber traits to traits dictionary.
cf.CFDictionaryAddValue(traitsDict, cocoapy.kCTFontSymbolicTrait, symTraits)
# Add traits dictionary to attributes.
cf.CFDictionaryAddValue(attributes, cocoapy.kCTFontTraitsAttribute, traitsDict)
cf.CFRelease(traitsDict)
cf.CFRelease(symTraits)
# Create font descriptor with attributes.
descriptor = c_void_p(ct.CTFontDescriptorCreateWithAttributes(attributes))
cf.CFRelease(attributes)
return descriptor
def __init__(self, name, size, bold=False, italic=False, stretch=False, dpi=None):
# 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 = name or 'Helvetica'
# I don't know what is the right thing to do here.
dpi = dpi or 96
size = size * dpi / 72.0
# Construct traits value.
traits = 0
if bold:
traits |= cocoapy.kCTFontBoldTrait
if italic:
traits |= cocoapy.kCTFontItalicTrait
name = str(name)
# First see if we can find an appropriate font from our table of loaded fonts.
cgFont = self._lookup_font_with_family_and_traits(name, traits)
if cgFont:
# Use cgFont from table to create a CTFont object with the specified size.
self.ctFont = c_void_p(ct.CTFontCreateWithGraphicsFont(cgFont, size, None, None))
else:
# Create a font descriptor for given name and traits and use it to create font.
descriptor = self._create_font_descriptor(name, traits)
self.ctFont = c_void_p(ct.CTFontCreateWithFontDescriptor(descriptor, size, None))
cf.CFRelease(descriptor)
assert self.ctFont, "Couldn't load font: " + name
string = c_void_p(ct.CTFontCopyFamilyName(self.ctFont))
self._family_name = str(cocoapy.cfstring_to_string(string))
cf.CFRelease(string)
self.ascent = int(math.ceil(ct.CTFontGetAscent(self.ctFont)))
self.descent = -int(math.ceil(ct.CTFontGetDescent(self.ctFont)))
@property
def name(self):
return self._family_name
def __del__(self):
cf.CFRelease(self.ctFont)
@classmethod
def have_font(cls, name):
name = str(name)
if name in cls._loaded_CGFont_table: return True
# Try to create the font to see if it exists.
# TODO: Find a better way to check.
cfstring = cocoapy.CFSTR(name)
cgfont = c_void_p(quartz.CGFontCreateWithFontName(cfstring))
cf.CFRelease(cfstring)
if cgfont:
cf.CFRelease(cgfont)
return True
return False
@classmethod
def add_font_data(cls, data):
# Create a cgFont with the data. There doesn't seem to be a way to
# register a font loaded from memory such that the operating system will
# find it later. So instead we just store the cgFont in a table where
# it can be found by our __init__ method.
# Note that the iOS CTFontManager *is* able to register graphics fonts,
# however this method is missing from CTFontManager on MacOS 10.6
dataRef = c_void_p(cf.CFDataCreate(None, data, len(data)))
provider = c_void_p(quartz.CGDataProviderCreateWithCFData(dataRef))
cgFont = c_void_p(quartz.CGFontCreateWithDataProvider(provider))
cf.CFRelease(dataRef)
quartz.CGDataProviderRelease(provider)
# Create a template CTFont from the graphics font so that we can get font info.
ctFont = c_void_p(ct.CTFontCreateWithGraphicsFont(cgFont, 1, None, None))
# Get info about the font to use as key in our font table.
string = c_void_p(ct.CTFontCopyFamilyName(ctFont))
familyName = str(cocoapy.cfstring_to_string(string))
cf.CFRelease(string)
string = c_void_p(ct.CTFontCopyFullName(ctFont))
fullName = str(cocoapy.cfstring_to_string(string))
cf.CFRelease(string)
traits = ct.CTFontGetSymbolicTraits(ctFont)
cf.CFRelease(ctFont)
# Store font in table. We store it under both its family name and its
# full name, since its not always clear which one will be looked up.
if familyName not in cls._loaded_CGFont_table:
cls._loaded_CGFont_table[familyName] = {}
cls._loaded_CGFont_table[familyName][traits] = cgFont
if fullName not in cls._loaded_CGFont_table:
cls._loaded_CGFont_table[fullName] = {}
cls._loaded_CGFont_table[fullName][traits] = cgFont
|