# mypy: allow-untyped-defs
import ctypes
import os
import platform
import plistlib
from shutil import copy2, rmtree
from subprocess import call, check_output
HERE = os.path.dirname(__file__)
SYSTEM = platform.system().lower()
class FontInstaller:
def __init__(self, logger, font_dir=None, **fonts):
self.logger = logger
self.font_dir = font_dir
self.installed_fonts = False
self.created_dir = False
self.fonts = fonts
def __call__(self, env_options=None, env_config=None):
return self
def __enter__(self):
for _, font_path in self.fonts.items():
font_name = font_path.split('/')[-1]
install = getattr(self, 'install_%s_font' % SYSTEM, None)
if not install:
self.logger.warning('Font installation not supported on %s' % SYSTEM)
return False
if install(font_name, font_path):
self.installed_fonts = True
self.logger.info('Installed font: %s' % font_name)
else:
self.logger.warning('Unable to install font: %s' % font_name)
def __exit__(self, exc_type, exc_val, exc_tb):
if not self.installed_fonts:
return False
for _, font_path in self.fonts.items():
font_name = font_path.split('/')[-1]
remove = getattr(self, 'remove_%s_font' % SYSTEM, None)
if not remove:
self.logger.warning('Font removal not supported on %s' % SYSTEM)
return False
if remove(font_name, font_path):
self.logger.info('Removed font: %s' % font_name)
else:
self.logger.warning('Unable to remove font: %s' % font_name)
def install_linux_font(self, font_name, font_path):
if not self.font_dir:
self.font_dir = os.path.join(os.path.expanduser('~'), '.fonts')
if not os.path.exists(self.font_dir):
os.makedirs(self.font_dir)
self.created_dir = True
if not os.path.exists(os.path.join(self.font_dir, font_name)):
copy2(font_path, self.font_dir)
try:
fc_cache_returncode = call('fc-cache')
return not fc_cache_returncode
except OSError: # If fontconfig doesn't exist, return False
self.logger.error('fontconfig not available on this Linux system.')
return False
def install_darwin_font(self, font_name, font_path):
if not self.font_dir:
self.font_dir = os.path.join(os.path.expanduser('~'),
'Library/Fonts')
if not os.path.exists(self.font_dir):
os.makedirs(self.font_dir)
self.created_dir = True
installed_font_path = os.path.join(self.font_dir, font_name)
if not os.path.exists(installed_font_path):
copy2(font_path, self.font_dir)
# Per https://github.com/web-platform-tests/results-collection/issues/218
# installing Ahem on macOS is flaky, so check if it actually installed
with open(os.devnull, 'w') as f:
fonts = check_output(['/usr/sbin/system_profiler', '-xml', 'SPFontsDataType'], stderr=f)
try:
# if py3
load_plist = plistlib.loads
except AttributeError:
load_plist = plistlib.readPlistFromString
fonts = load_plist(fonts)
assert len(fonts) == 1
for font in fonts[0]['_items']:
if font['path'] == installed_font_path:
return True
return False
def install_windows_font(self, _, font_path):
hwnd_broadcast = 0xFFFF
wm_fontchange = 0x001D
gdi32 = ctypes.WinDLL('gdi32')
if gdi32.AddFontResourceW(font_path):
from ctypes import wintypes
wparam = 0
lparam = 0
SendNotifyMessageW = ctypes.windll.user32.SendNotifyMessageW
SendNotifyMessageW.argtypes = [wintypes.HANDLE, wintypes.UINT,
wintypes.WPARAM, wintypes.LPARAM]
return bool(SendNotifyMessageW(hwnd_broadcast, wm_fontchange,
wparam, lparam))
def remove_linux_font(self, font_name, _):
if self.created_dir:
rmtree(self.font_dir)
else:
os.remove(f'{self.font_dir}/{font_name}')
try:
fc_cache_returncode = call('fc-cache')
return not fc_cache_returncode
except OSError: # If fontconfig doesn't exist, return False
self.logger.error('fontconfig not available on this Linux system.')
return False
def remove_darwin_font(self, font_name, _):
if self.created_dir:
rmtree(self.font_dir)
else:
os.remove(os.path.join(self.font_dir, font_name))
return True
def remove_windows_font(self, _, font_path):
hwnd_broadcast = 0xFFFF
wm_fontchange = 0x001D
gdi32 = ctypes.WinDLL('gdi32')
if gdi32.RemoveFontResourceW(font_path):
from ctypes import wintypes
wparam = 0
lparam = 0
SendNotifyMessageW = ctypes.windll.user32.SendNotifyMessageW
SendNotifyMessageW.argtypes = [wintypes.HANDLE, wintypes.UINT,
wintypes.WPARAM, wintypes.LPARAM]
return bool(SendNotifyMessageW(hwnd_broadcast, wm_fontchange,
wparam, lparam))