Search code examples
pythonwindowsbatch-filefonts

Install a font using python in Windows


I am developing a python script to install a list of fonts in Windows. I have a list like this,

fonts_list = ["/path/to/file/myFont1.ttf", "/path/to/file/myFont1.ttf", "/path/to/file/myFont1.ttf"]

I simply tried, copying font files using both shutil and os, and after that I tried adding it to Windows registry, even with the admin privileges, it didn't work. Output from the script did not display any errors but there is no any font in Windows Fonts directory which are mentioned in the list.

os.system(reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "FontName (TrueType)")

So I started seeking more seeds from web and then I found this one, Installing TTF fonts on windows with python. But it didn't work too. Next I tried calling Windows fontview.exe, but for a list of fonts it is a disaster.

os.system("fontview " + fontsList[0])

Is there anyway to install a font in windows programmatically? Or is there any user level directory in Windows to simply copy font files like Linux systems have ~/.fonts and Macs have ~/Library/Fonts? Please help.


Solution

  • Here's an install_font function to copy a font to the system Fonts folder, load it in the current session, notify running programs, and update the registry. It only depends on Python's standard library and should work in both Python 2 and 3.

    ctypes definitions

    import os
    import shutil
    import ctypes
    from ctypes import wintypes
    
    try:
        import winreg
    except ImportError:
        import _winreg as winreg
    
    user32 = ctypes.WinDLL('user32', use_last_error=True)
    gdi32 = ctypes.WinDLL('gdi32', use_last_error=True)
    
    FONTS_REG_PATH = r'Software\Microsoft\Windows NT\CurrentVersion\Fonts'
    
    HWND_BROADCAST   = 0xFFFF
    SMTO_ABORTIFHUNG = 0x0002
    WM_FONTCHANGE    = 0x001D
    GFRI_DESCRIPTION = 1
    GFRI_ISTRUETYPE  = 3
    
    if not hasattr(wintypes, 'LPDWORD'):
        wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
    
    user32.SendMessageTimeoutW.restype = wintypes.LPVOID
    user32.SendMessageTimeoutW.argtypes = (
        wintypes.HWND,   # hWnd
        wintypes.UINT,   # Msg
        wintypes.LPVOID, # wParam
        wintypes.LPVOID, # lParam
        wintypes.UINT,   # fuFlags
        wintypes.UINT,   # uTimeout
        wintypes.LPVOID) # lpdwResult
    
    gdi32.AddFontResourceW.argtypes = (
        wintypes.LPCWSTR,) # lpszFilename
    
    # http://www.undocprint.org/winspool/getfontresourceinfo
    gdi32.GetFontResourceInfoW.argtypes = (
        wintypes.LPCWSTR, # lpszFilename
        wintypes.LPDWORD, # cbBuffer
        wintypes.LPVOID,  # lpBuffer
        wintypes.DWORD)   # dwQueryType
    

    function definition

    def install_font(src_path):
        # copy the font to the Windows Fonts folder
        dst_path = os.path.join(os.environ['SystemRoot'], 'Fonts',
                                os.path.basename(src_path))
        shutil.copy(src_path, dst_path)
        # load the font in the current session
        if not gdi32.AddFontResourceW(dst_path):
            os.remove(dst_path)
            raise WindowsError('AddFontResource failed to load "%s"' % src_path)
        # notify running programs
        user32.SendMessageTimeoutW(HWND_BROADCAST, WM_FONTCHANGE, 0, 0,
                                   SMTO_ABORTIFHUNG, 1000, None)
        # store the fontname/filename in the registry
        filename = os.path.basename(dst_path)
        fontname = os.path.splitext(filename)[0]
        # try to get the font's real name
        cb = wintypes.DWORD()
        if gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb), None,
                                      GFRI_DESCRIPTION):
            buf = (ctypes.c_wchar * cb.value)()
            if gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb), buf,
                                          GFRI_DESCRIPTION):
                fontname = buf.value
        is_truetype = wintypes.BOOL()
        cb.value = ctypes.sizeof(is_truetype)
        gdi32.GetFontResourceInfoW(filename, ctypes.byref(cb),
            ctypes.byref(is_truetype), GFRI_ISTRUETYPE)
        if is_truetype:
            fontname += ' (TrueType)'
        with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, FONTS_REG_PATH, 0,
                            winreg.KEY_SET_VALUE) as key:
            winreg.SetValueEx(key, fontname, 0, winreg.REG_SZ, filename)