Search code examples
pythonwindowsfonts

How to get all installed font path/filename on Windows?


How to get all installed font path in python on Windows?

I would like to use GDI or DirectWrite.


Solution

  • Solution with DirectWrite

    Dependencies:

    The solution contains 2 methods since the one for Windows 10 is much faster then the one for Windows 7 or more.

    import comtypes
    import sys
    import time
    from ctypes import byref, create_unicode_buffer, POINTER, windll, wintypes
    from enum import IntEnum
    from typing import Set
    
    
    DWriteCreateFactory = windll.dwrite.DWriteCreateFactory
    DWriteCreateFactory.restype = comtypes.HRESULT
    DWriteCreateFactory.argtypes = [wintypes.UINT, comtypes.GUID, POINTER(POINTER(comtypes.IUnknown))]
    
    
    class DWRITE_FACTORY_TYPE(IntEnum):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/ne-dwrite-dwrite_factory_type
        DWRITE_FACTORY_TYPE_SHARED = 0
        DWRITE_FACTORY_TYPE_ISOLATED = 1
    
    
    class IDWriteFontFileLoader(comtypes.IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontfileloader
        _iid_ = comtypes.GUID("{727cad4e-d6af-4c9e-8a08-d695b11caa49}")
        _methods_ = [
            comtypes.STDMETHOD(None, "CreateStreamFromKey"), # Need to be implemented
        ]
    
    
    class IDWriteLocalFontFileLoader(IDWriteFontFileLoader):
        # https://learn.microsoft.com/en-us/windows/win32/directwrite/idwritelocalfontfileloader
        _iid_ = comtypes.GUID("{b2d9f3ec-c9fe-4a11-a2ec-d86208f7c0a2}")
        _methods_ = [
            comtypes.STDMETHOD(None, "GetFilePathLengthFromKey", [wintypes.LPCVOID, wintypes.UINT, POINTER(wintypes.UINT)]),
            comtypes.STDMETHOD(None, "GetFilePathFromKey",  [wintypes.LPCVOID, wintypes.UINT, POINTER(wintypes.WCHAR), wintypes.UINT]),
            comtypes.STDMETHOD(None, "GetLastWriteTimeFromKey"), # Need to be implemented
        ]
    
    
    class IDWriteFontFile(comtypes.IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontfile
        _iid_ = comtypes.GUID("{739d886a-cef5-47dc-8769-1a8b41bebbb0}")
        _methods_ = [
            comtypes.STDMETHOD(comtypes.HRESULT, "GetReferenceKey", [POINTER(wintypes.LPCVOID), POINTER(wintypes.UINT)]),
            comtypes.STDMETHOD(comtypes.HRESULT, "GetLoader", [POINTER(POINTER(IDWriteFontFileLoader))]),
            comtypes.STDMETHOD(None, "Analyze"), # Need to be implemented
        ]
    
    
    class IDWriteFontFaceReference(comtypes.IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontfacereference
        _iid_ = comtypes.GUID("{5E7FA7CA-DDE3-424C-89F0-9FCD6FED58CD}")
        _methods_ = [
            comtypes.STDMETHOD(None, "CreateFontFace"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateFontFaceWithSimulations"), # Need to be implemented
            comtypes.STDMETHOD(None, "Equals"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetFontFaceIndex"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetSimulations"), # Need to be implemented
            comtypes.STDMETHOD(comtypes.HRESULT, "GetFontFile", [POINTER(POINTER(IDWriteFontFile))]),
            comtypes.STDMETHOD(None, "GetLocalFileSize"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetFileSize"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetFileTime"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetLocality"), # Need to be implemented
            comtypes.STDMETHOD(None, "EnqueueFontDownloadRequest"), # Need to be implemented
            comtypes.STDMETHOD(None, "EnqueueCharacterDownloadRequest"), # Need to be implemented
            comtypes.STDMETHOD(None, "EnqueueGlyphDownloadRequest"), # Need to be implemented
            comtypes.STDMETHOD(None, "EnqueueFileFragmentDownloadRequest"), # Need to be implemented
        ]
    
    
    class IDWriteFontSet(comtypes.IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontset
        _iid_ = comtypes.GUID("{53585141-D9F8-4095-8321-D73CF6BD116B}")
        _methods_ = [
            comtypes.STDMETHOD(wintypes.UINT, "GetFontCount"),
            comtypes.STDMETHOD(comtypes.HRESULT, "GetFontFaceReference", [wintypes.UINT, POINTER(POINTER(IDWriteFontFaceReference))]),
            comtypes.STDMETHOD(None, "FindFontFaceReference"), # Need to be implemented
            comtypes.STDMETHOD(None, "FindFontFace"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetPropertyValues"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetPropertyValues"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetPropertyValues"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetPropertyOccurrenceCount"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetMatchingFonts"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetMatchingFonts"), # Need to be implemented
        ]
    
    
    class IDWriteFontFace(comtypes.IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontface
        _iid_ = comtypes.GUID("{5f49804d-7024-4d43-bfa9-d25984f53849}")
        _methods_ = [
            comtypes.STDMETHOD(None, "GetType"), # Need to be implemented
            comtypes.STDMETHOD(comtypes.HRESULT, "GetFiles", [POINTER(wintypes.UINT), POINTER(POINTER(IDWriteFontFile))]),
            comtypes.STDMETHOD(None, "GetIndex"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetSimulations"), # Need to be implemented
            comtypes.STDMETHOD(None, "IsSymbolFont"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetMetrics"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetGlyphCount"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetDesignGlyphMetrics"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetGlyphIndices"), # Need to be implemented
            comtypes.STDMETHOD(None, "TryGetFontTable"), # Need to be implemented
            comtypes.STDMETHOD(None, "ReleaseFontTable"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetGlyphRunOutline"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetRecommendedRenderingMode"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetGdiCompatibleMetrics"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetGdiCompatibleGlyphMetrics"), # Need to be implemented
        ]
    
    
    class IDWriteFont(comtypes.IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefont
        _iid_ = comtypes.GUID("{acd16696-8c14-4f5d-877e-fe3fc1d32737}")
        _methods_ = [
            comtypes.STDMETHOD(None, "GetFontFamily"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetWeight"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetStretch"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetStyle"), # Need to be implemented
            comtypes.STDMETHOD(None, "IsSymbolFont"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetFaceNames"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetInformationalStrings"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetSimulations"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetMetrics"), # Need to be implemented
            comtypes.STDMETHOD(None, "HasCharacter"), # Need to be implemented
            comtypes.STDMETHOD(comtypes.HRESULT, "CreateFontFace", [POINTER(POINTER(IDWriteFontFace))]),
        ]
    
    
    class IDWriteFontList(comtypes.IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontlist
        _iid_ = comtypes.GUID("{1a0d8438-1d97-4ec1-aef9-a2fb86ed6acb}")
        _methods_ = [
            comtypes.STDMETHOD(None, "GetFontCollection"), # Need to be implemented
            comtypes.STDMETHOD(wintypes.UINT, "GetFontCount"),
            comtypes.STDMETHOD(comtypes.HRESULT, "GetFont", [wintypes.UINT, POINTER(POINTER(IDWriteFont))]),
        ]
    
    
    class IDWriteFontFamily(IDWriteFontList):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontfamily
        _iid_ = comtypes.GUID("{da20d8ef-812a-4c43-9802-62ec4abd7add}")
        _methods_ = [
            comtypes.STDMETHOD(None, "GetFamilyNames"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetFirstMatchingFont"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetMatchingFonts"), # Need to be implemented
        ]
    
    
    class IDWriteFontCollection(comtypes.IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontcollection
        _iid_ = comtypes.GUID("{a84cee02-3eea-4eee-a827-87c1a02a0fcc}")
        _methods_ = [
            comtypes.STDMETHOD(wintypes.UINT, "GetFontFamilyCount"),
            comtypes.STDMETHOD(comtypes.HRESULT, "GetFontFamily", [wintypes.UINT, POINTER(POINTER(IDWriteFontFamily))]),
            comtypes.STDMETHOD(None, "FindFamilyName"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetFontFromFontFace"), # Need to be implemented
        ]
    
    
    class IDWriteFactory(comtypes.IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefactory
        _iid_ = comtypes.GUID("{b859ee5a-d838-4b5b-a2e8-1adc7d93db48}")
        _methods_ = [
            comtypes.STDMETHOD(comtypes.HRESULT, "GetSystemFontCollection", [POINTER(POINTER(IDWriteFontCollection)), wintypes.BOOLEAN]),
            comtypes.STDMETHOD(None, "CreateCustomFontCollection"), # Need to be implemented
            comtypes.STDMETHOD(None, "RegisterFontCollectionLoader"), # Need to be implemented
            comtypes.STDMETHOD(None, "UnregisterFontCollectionLoader"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateFontFileReference"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateCustomFontFileReference"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateFontFace"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateRenderingParams"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateMonitorRenderingParams"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateCustomRenderingParams"), # Need to be implemented
            comtypes.STDMETHOD(None, "RegisterFontFileLoader"), # Need to be implemented
            comtypes.STDMETHOD(None, "UnregisterFontFileLoader"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateTextFormat"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateTypography"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetGdiInterop"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateTextLayout"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateGdiCompatibleTextLayout"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateEllipsisTrimmingSign"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateTextAnalyzer"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateNumberSubstitution"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateGlyphRunAnalysis"), # Need to be implemented
        ]
    
    
    class IDWriteFactory1(IDWriteFactory):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite_1/nn-dwrite_1-idwritefactory1
        _iid_ = comtypes.GUID("{30572f99-dac6-41db-a16e-0486307e606a}")
        _methods_ = [
            comtypes.STDMETHOD(None, "GetEudcFontCollection"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateCustomRenderingParams1"), # Need to be implemented
        ]
    
    
    class IDWriteFactory2(IDWriteFactory1):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite_2/nn-dwrite_2-idwritefactory2
        _iid_ = comtypes.GUID("{0439fc60-ca44-4994-8dee-3a9af7b732ec}")
        _methods_ = [
            comtypes.STDMETHOD(None, "GetSystemFontFallback"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateFontFallbackBuilder"), # Need to be implemented
            comtypes.STDMETHOD(None, "TranslateColorGlyphRun"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateCustomRenderingParams2"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateGlyphRunAnalysis"), # Need to be implemented
        ]
    
    
    class IDWriteFactory3(IDWriteFactory2):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefactory3
        _iid_ = comtypes.GUID("{9A1B41C3-D3BB-466A-87FC-FE67556A3B65}")
        _methods_ = [
            comtypes.STDMETHOD(None, "CreateGlyphRunAnalysis"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateCustomRenderingParams3"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateFontFaceReference"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateFontFaceReference"), # Need to be implemented
            comtypes.STDMETHOD(comtypes.HRESULT, "GetSystemFontSet", [POINTER(POINTER(IDWriteFontSet))]),
            comtypes.STDMETHOD(None, "CreateFontSetBuilder"), # Need to be implemented
            comtypes.STDMETHOD(None, "CreateFontCollectionFromFontSet"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetSystemFontCollection3"), # Need to be implemented
            comtypes.STDMETHOD(None, "GetFontDownloadQueue"), # Need to be implemented
        ]
    
    
    def get_fonts_filepath_windows_10_or_more() -> Set[str]:
        """
        Return an list of all the font installed.
        """
        fonts_path = set()
    
        dwrite_factory = POINTER(IDWriteFactory3)()
        DWriteCreateFactory(DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_ISOLATED, IDWriteFactory3._iid_, byref(dwrite_factory))
    
        font_set = POINTER(IDWriteFontSet)()
        dwrite_factory.GetSystemFontSet(byref(font_set))
    
        for i in range(font_set.GetFontCount()):
            font_face_reference = POINTER(IDWriteFontFaceReference)()
            font_set.GetFontFaceReference(i, byref(font_face_reference))
    
            font_file = POINTER(IDWriteFontFile)()
            font_face_reference.GetFontFile(byref(font_file))
    
            loader = POINTER(IDWriteFontFileLoader)()
            font_file.GetLoader(byref(loader))
    
            font_file_reference_key = wintypes.LPCVOID()
            font_file_reference_key_size = wintypes.UINT()
            font_file.GetReferenceKey(byref(font_file_reference_key), byref(font_file_reference_key_size))
    
            try:
                local_loader = loader.QueryInterface(IDWriteLocalFontFileLoader)
            except comtypes.COMError:
                continue
    
            path_len = wintypes.UINT()
            local_loader.GetFilePathLengthFromKey(font_file_reference_key, font_file_reference_key_size, byref(path_len))
    
            buffer = create_unicode_buffer(path_len.value + 1)
            local_loader.GetFilePathFromKey(font_file_reference_key, font_file_reference_key_size, buffer, len(buffer))
    
            fonts_path.add(buffer.value)
    
        return fonts_path
    
    
    def get_fonts_filepath_windows_7_or_more() -> Set[str]:
        """
        Return an list of all the font installed.
        """
        fonts_path = set()
    
        dwrite_factory = POINTER(IDWriteFactory)()
        DWriteCreateFactory(DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_ISOLATED, IDWriteFactory._iid_, byref(dwrite_factory))
    
        sys_collection = POINTER(IDWriteFontCollection)()
        dwrite_factory.GetSystemFontCollection(byref(sys_collection), False)
    
        for i in range(sys_collection.GetFontFamilyCount()):
            family = POINTER(IDWriteFontFamily)()
            sys_collection.GetFontFamily(i, byref(family))
    
            for j in range(family.GetFontCount()):
                font = POINTER(IDWriteFont)()
                family.GetFont(j, byref(font))
    
                font_face = POINTER(IDWriteFontFace)()
                font.CreateFontFace(byref(font_face))
    
                file_count = wintypes.UINT()
                font_face.GetFiles(byref(file_count), None)
    
                font_files = (POINTER(IDWriteFontFile)* file_count.value)()
                font_face.GetFiles(byref(file_count), font_files)
    
                for font_file in font_files:
                    font_file_reference_key = wintypes.LPCVOID()
                    font_file_reference_key_size = wintypes.UINT()
                    font_file.GetReferenceKey(byref(font_file_reference_key), byref(font_file_reference_key_size))
    
                    loader = POINTER(IDWriteFontFileLoader)()
                    font_file.GetLoader(byref(loader))
    
                    try:
                        local_loader = loader.QueryInterface(IDWriteLocalFontFileLoader)
                    except comtypes.COMError:
                        continue
    
                    path_len = wintypes.UINT()
                    local_loader.GetFilePathLengthFromKey(font_file_reference_key, font_file_reference_key_size, byref(path_len))
    
                    buffer = create_unicode_buffer(path_len.value + 1)
                    local_loader.GetFilePathFromKey(font_file_reference_key, font_file_reference_key_size, buffer, len(buffer))
    
                    fonts_path.add(buffer.value)
    
        return fonts_path
    
    
    def is_windows_version_or_greater(windows_version, major: int, minor: int, service_pack_major: int) -> bool:
        """
        Parameters:
            windows_version: An object from sys.getwindowsversion.
            major (int): The minimum major OS version number.
            minor (int): The minimum minor OS version number.
            service_pack_major (int): The minimum major Service Pack version number.
        Returns:
            True if the specified version matches or if it is greater than the version of the current Windows OS. Otherwise, False.
        """
    
        if windows_version.major > major:
            return True
        elif windows_version.major == major and windows_version.minor > minor:
            return True
        else:
            return windows_version.major == major and windows_version.minor == minor and windows_version.service_pack_major >= service_pack_major
    
    
    def is_windows_vista_sp2_or_greater(windows_version) -> bool:
        return is_windows_version_or_greater(windows_version, 6, 0, 2)
    
    
    def is_windows_10_or_greater(windows_version) -> bool:
        return is_windows_version_or_greater(windows_version, 10, 0, 0)
    
    
    def main():
        start = time.time()
    
        if sys.platform == "win32":
            windows_version = sys.getwindowsversion()
    
            if is_windows_10_or_greater(windows_version):
                fonts_path_dwrite = get_fonts_filepath_windows_10_or_more()
            elif is_windows_vista_sp2_or_greater(windows_version):
                fonts_path_dwrite = get_fonts_filepath_windows_7_or_more()
            else:
                raise Exception("This script only works on Windows 7 or more")
        else:
            raise Exception("This script only works on Windows.")
    
        print(fonts_path_dwrite)
        print(f"Time elapsed: {time.time() - start}")
    
    
    if __name__ == "__main__":
        sys.exit(main())
    

    Solution with Windows registry key

    Dependencies:

    import os
    import sys
    import time
    import winreg
    from win32com.shell import shell, shellcon
    
    
    def get_win32_system_font():
        with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts") as key:
            paths = []
            info = winreg.QueryInfoKey(key)
            fonts_root = shell.SHGetKnownFolderPath(shellcon.FOLDERID_Fonts)
    
            for index in range(info[1]):
                value = winreg.EnumValue(key, index)
                path = value[1] if os.path.isabs(value[1]) else os.path.join(fonts_root, value[1])
                paths.append(path)
            
            return paths
    
    def get_win32_user_font():
        with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts") as key:
            paths = []
            info = winreg.QueryInfoKey(key)
            for index in range(info[1]):
                value = winreg.EnumValue(key, index)
                paths.append(value[1])
            
            return paths
    
    
    def main():
        start = time.time()
        system_font = get_win32_system_font()
        user_font = get_win32_user_font()
        print(time.time() - start)
    
    
    if __name__ == "__main__":
        sys.exit(main())
    

    Solution with matplotlib

    Dependencies:

    import sys
    import time
    from matplotlib import font_manager
    
    def main():
        start = time.time()
        fonts_paths  = set(font_manager.findSystemFonts())
        print(time.time() - start)
    
    
    if __name__ == "__main__":
        sys.exit(main())
    

    Solution with FindSystemFontsFilename

    Dependencies:

    from find_system_fonts_filename import get_system_fonts_filename, FontConfigNotFound, OSNotSupported
    
    try:
        fonts_filename = get_system_fonts_filename()
    except (FontConfigNotFound, OSNotSupported):
        # Deal with the exception
        # OSNotSupported can only happen in Windows and macOS
        #   - Windows Vista SP2 and more are supported
        #   - macOS 10.6 and more are supported
        # FontConfigNotFound can only happen on Linux when Fontconfig could't be found.
        pass