sysfont.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. # coding: ascii
  2. # pygame - Python Game Library
  3. # Copyright (C) 2000-2003 Pete Shinners
  4. #
  5. # This library is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU Library General Public
  7. # License as published by the Free Software Foundation; either
  8. # version 2 of the License, or (at your option) any later version.
  9. #
  10. # This library is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. # Library General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Library General Public
  16. # License along with this library; if not, write to the Free
  17. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  18. #
  19. # Pete Shinners
  20. # pete@shinners.org
  21. """sysfont, used in the font module to find system fonts"""
  22. import os
  23. import sys
  24. from pygame.compat import xrange_, PY_MAJOR_VERSION
  25. from os.path import basename, dirname, exists, join, splitext
  26. import xml.etree.ElementTree as ET
  27. OpenType_extensions = frozenset(('.ttf', '.ttc', '.otf'))
  28. Sysfonts = {}
  29. Sysalias = {}
  30. # Python 3 compatibility
  31. if PY_MAJOR_VERSION >= 3:
  32. def toascii(raw):
  33. """convert bytes to ASCII-only string"""
  34. return raw.decode('ascii', 'ignore')
  35. if os.name == 'nt':
  36. import winreg as _winreg
  37. else:
  38. import subprocess
  39. else:
  40. def toascii(raw):
  41. """return ASCII characters of a given unicode or 8-bit string"""
  42. return raw.decode('ascii', 'ignore')
  43. if os.name == 'nt':
  44. import _winreg
  45. else:
  46. import subprocess
  47. def _simplename(name):
  48. """create simple version of the font name"""
  49. # return alphanumeric characters of a string (converted to lowercase)
  50. return ''.join(c.lower() for c in name if c.isalnum())
  51. def _addfont(name, bold, italic, font, fontdict):
  52. """insert a font and style into the font dictionary"""
  53. if name not in fontdict:
  54. fontdict[name] = {}
  55. fontdict[name][bold, italic] = font
  56. def initsysfonts_win32():
  57. """initialize fonts dictionary on Windows"""
  58. fontdir = join(os.environ.get('WINDIR', 'C:\\Windows'), 'Fonts')
  59. TrueType_suffix = '(TrueType)'
  60. mods = ('demibold', 'narrow', 'light', 'unicode', 'bt', 'mt')
  61. fonts = {}
  62. # add fonts entered in the registry
  63. # find valid registry keys containing font information.
  64. # http://docs.python.org/lib/module-sys.html
  65. # 0 (VER_PLATFORM_WIN32s) Win32s on Windows 3.1
  66. # 1 (VER_PLATFORM_WIN32_WINDOWS) Windows 95/98/ME
  67. # 2 (VER_PLATFORM_WIN32_NT) Windows NT/2000/XP
  68. # 3 (VER_PLATFORM_WIN32_CE) Windows CE
  69. if sys.getwindowsversion()[0] == 1:
  70. key_name = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Fonts"
  71. else:
  72. key_name = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"
  73. key = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, key_name)
  74. for i in xrange_(_winreg.QueryInfoKey(key)[1]):
  75. try:
  76. # name is the font's name e.g. Times New Roman (TrueType)
  77. # font is the font's filename e.g. times.ttf
  78. name, font = _winreg.EnumValue(key, i)[0:2]
  79. except EnvironmentError:
  80. break
  81. # try to handle windows unicode strings for file names with
  82. # international characters
  83. if PY_MAJOR_VERSION < 3:
  84. # here are two documents with some information about it:
  85. # http://www.python.org/peps/pep-0277.html
  86. # https://www.microsoft.com/technet/archive/interopmigration/linux/mvc/lintowin.mspx#ECAA
  87. try:
  88. font = str(font)
  89. except UnicodeEncodeError:
  90. # MBCS is the windows encoding for unicode file names.
  91. try:
  92. font = font.encode('MBCS')
  93. except:
  94. # no success with str or MBCS encoding... skip this font.
  95. continue
  96. if splitext(font)[1].lower() not in OpenType_extensions:
  97. continue
  98. if not dirname(font):
  99. font = join(fontdir, font)
  100. if name.endswith(TrueType_suffix):
  101. name = name.rstrip(TrueType_suffix).rstrip()
  102. name = name.lower().split()
  103. bold = italic = 0
  104. for m in mods:
  105. if m in name:
  106. name.remove(m)
  107. if 'bold' in name:
  108. name.remove('bold')
  109. bold = 1
  110. if 'italic' in name:
  111. name.remove('italic')
  112. italic = 1
  113. name = ''.join(name)
  114. name = _simplename(name)
  115. _addfont(name, bold, italic, font, fonts)
  116. return fonts
  117. def _add_font_paths(sub_elements, fonts):
  118. """ Gets each element, checks its tag content,
  119. if wanted fetches the next value in the iterable
  120. """
  121. font_name = font_path = None
  122. for tag in sub_elements:
  123. if tag.text == "_name":
  124. font_name = next(sub_elements).text
  125. if splitext(font_name)[1] not in OpenType_extensions:
  126. break
  127. bold = "bold" in font_name
  128. italic = "italic" in font_name
  129. if tag.text == "path" and font_name is not None:
  130. font_path = next(sub_elements).text
  131. _addfont(_simplename(font_name),bold,italic,font_path,fonts)
  132. break
  133. def _system_profiler_darwin():
  134. fonts = {}
  135. flout, flerr = subprocess.Popen(
  136. ' '.join(['system_profiler', '-xml','SPFontsDataType']),
  137. shell=True,
  138. stdout=subprocess.PIPE,
  139. stderr=subprocess.PIPE,
  140. close_fds=True
  141. ).communicate()
  142. for font_node in ET.fromstring(flout).iterfind('./array/dict/array/dict'):
  143. _add_font_paths(font_node.iter("*"), fonts)
  144. return fonts
  145. def initsysfonts_darwin():
  146. """ Read the fonts on MacOS, and OS X.
  147. """
  148. # if the X11 binary exists... try and use that.
  149. # Not likely to be there on pre 10.4.x ... or MacOS 10.10+
  150. if exists('/usr/X11/bin/fc-list'):
  151. fonts = initsysfonts_unix('/usr/X11/bin/fc-list')
  152. # This fc-list path will work with the X11 from the OS X 10.3 installation
  153. # disc
  154. elif exists('/usr/X11R6/bin/fc-list'):
  155. fonts = initsysfonts_unix('/usr/X11R6/bin/fc-list')
  156. elif exists('/usr/sbin/system_profiler'):
  157. try:
  158. fonts = _system_profiler_darwin()
  159. except:
  160. fonts = {}
  161. else:
  162. fonts = {}
  163. return fonts
  164. # read the fonts on unix
  165. def initsysfonts_unix(path="fc-list"):
  166. """use the fc-list from fontconfig to get a list of fonts"""
  167. fonts = {}
  168. try:
  169. # note, we capture stderr so if fc-list isn't there to stop stderr
  170. # printing.
  171. flout, flerr = subprocess.Popen('%s : file family style' % path, shell=True,
  172. stdout=subprocess.PIPE, stderr=subprocess.PIPE,
  173. close_fds=True).communicate()
  174. except Exception:
  175. return fonts
  176. entries = toascii(flout)
  177. try:
  178. for line in entries.split('\n'):
  179. try:
  180. filename, family, style = line.split(':', 2)
  181. if splitext(filename)[1].lower() in OpenType_extensions:
  182. bold = 'Bold' in style
  183. italic = 'Italic' in style
  184. oblique = 'Oblique' in style
  185. for name in family.strip().split(','):
  186. if name:
  187. break
  188. else:
  189. name = splitext(basename(filename))[0]
  190. _addfont(
  191. _simplename(name), bold, italic or oblique, filename, fonts)
  192. except Exception:
  193. # try the next one.
  194. pass
  195. except Exception:
  196. pass
  197. return fonts
  198. def create_aliases():
  199. """map common fonts that are absent from the system to similar fonts that are installed in the system"""
  200. alias_groups = (
  201. ('monospace', 'misc-fixed', 'courier', 'couriernew', 'console',
  202. 'fixed', 'mono', 'freemono', 'bitstreamverasansmono',
  203. 'verasansmono', 'monotype', 'lucidaconsole'),
  204. ('sans', 'arial', 'helvetica', 'swiss', 'freesans',
  205. 'bitstreamverasans', 'verasans', 'verdana', 'tahoma'),
  206. ('serif', 'times', 'freeserif', 'bitstreamveraserif', 'roman',
  207. 'timesroman', 'timesnewroman', 'dutch', 'veraserif',
  208. 'georgia'),
  209. ('wingdings', 'wingbats'),
  210. )
  211. for alias_set in alias_groups:
  212. for name in alias_set:
  213. if name in Sysfonts:
  214. found = Sysfonts[name]
  215. break
  216. else:
  217. continue
  218. for name in alias_set:
  219. if name not in Sysfonts:
  220. Sysalias[name] = found
  221. # initialize it all, called once
  222. def initsysfonts():
  223. if sys.platform == 'win32':
  224. fonts = initsysfonts_win32()
  225. elif sys.platform == 'darwin':
  226. fonts = initsysfonts_darwin()
  227. else:
  228. fonts = initsysfonts_unix()
  229. Sysfonts.update(fonts)
  230. create_aliases()
  231. if not Sysfonts: # dummy so we don't try to reinit
  232. Sysfonts[None] = None
  233. # pygame.font specific declarations
  234. def font_constructor(fontpath, size, bold, italic):
  235. import pygame.font
  236. font = pygame.font.Font(fontpath, size)
  237. if bold:
  238. font.set_bold(1)
  239. if italic:
  240. font.set_italic(1)
  241. return font
  242. # the exported functions
  243. def SysFont(name, size, bold=False, italic=False, constructor=None):
  244. """pygame.font.SysFont(name, size, bold=False, italic=False, constructor=None) -> Font
  245. create a pygame Font from system font resources
  246. This will search the system fonts for the given font
  247. name. You can also enable bold or italic styles, and
  248. the appropriate system font will be selected if available.
  249. This will always return a valid Font object, and will
  250. fallback on the builtin pygame font if the given font
  251. is not found.
  252. Name can also be a comma separated list of names, in
  253. which case set of names will be searched in order. Pygame
  254. uses a small set of common font aliases, if the specific
  255. font you ask for is not available, a reasonable alternative
  256. may be used.
  257. if optional contructor is provided, it must be a function with
  258. signature constructor(fontpath, size, bold, italic) which returns
  259. a Font instance. If None, a pygame.font.Font object is created.
  260. """
  261. if constructor is None:
  262. constructor = font_constructor
  263. if not Sysfonts:
  264. initsysfonts()
  265. gotbold = gotitalic = False
  266. fontname = None
  267. if name:
  268. allnames = name
  269. for name in allnames.split(','):
  270. name = _simplename(name)
  271. styles = Sysfonts.get(name)
  272. if not styles:
  273. styles = Sysalias.get(name)
  274. if styles:
  275. plainname = styles.get((False, False))
  276. fontname = styles.get((bold, italic))
  277. if not fontname and not plainname:
  278. # Neither requested style, nor plain font exists, so
  279. # return a font with the name requested, but an
  280. # arbitrary style.
  281. (style, fontname) = list(styles.items())[0]
  282. # Attempt to style it as requested. This can't
  283. # unbold or unitalicize anything, but it can
  284. # fake bold and/or fake italicize.
  285. if bold and style[0]:
  286. gotbold = True
  287. if italic and style[1]:
  288. gotitalic = True
  289. elif not fontname:
  290. fontname = plainname
  291. elif plainname != fontname:
  292. gotbold = bold
  293. gotitalic = italic
  294. if fontname:
  295. break
  296. set_bold = set_italic = False
  297. if bold and not gotbold:
  298. set_bold = True
  299. if italic and not gotitalic:
  300. set_italic = True
  301. return constructor(fontname, size, set_bold, set_italic)
  302. def get_fonts():
  303. """pygame.font.get_fonts() -> list
  304. get a list of system font names
  305. Returns the list of all found system fonts. Note that
  306. the names of the fonts will be all lowercase with spaces
  307. removed. This is how pygame internally stores the font
  308. names for matching.
  309. """
  310. if not Sysfonts:
  311. initsysfonts()
  312. return list(Sysfonts)
  313. def match_font(name, bold=0, italic=0):
  314. """pygame.font.match_font(name, bold=0, italic=0) -> name
  315. find the filename for the named system font
  316. This performs the same font search as the SysFont()
  317. function, only it returns the path to the TTF file
  318. that would be loaded. The font name can be a comma
  319. separated list of font names to try.
  320. If no match is found, None is returned.
  321. """
  322. if not Sysfonts:
  323. initsysfonts()
  324. fontname = None
  325. allnames = name
  326. for name in allnames.split(','):
  327. name = _simplename(name)
  328. styles = Sysfonts.get(name)
  329. if not styles:
  330. styles = Sysalias.get(name)
  331. if styles:
  332. while not fontname:
  333. fontname = styles.get((bold, italic))
  334. if italic:
  335. italic = 0
  336. elif bold:
  337. bold = 0
  338. elif not fontname:
  339. fontname = list(styles.values())[0]
  340. if fontname:
  341. break
  342. return fontname