Search code examples
python-3.xwindowsdictionaryfonts

List all system fonts as dictionary. | python


I want to get all system fonts (inside c://Windows//Fonts) as dictionary since I need to differentiate between bold and italic etc. Though when listing the content of the directory via os.listdir or in terminal it's not possible to tell which font is what. (or at least in most cases) Further, even if you wanted to iterate through all fonts you could barely tell whether it's the 'regular' font or a variant.

So windows list the folder as follows:

normalexplorerview

Each of these 'font-folders' looks like (depending on their different styles) :

enter image description here

Lastly, this is what I get via the list command (unreadable and unusable for most cases):

enter image description here

So this is the output I wish I could achieve (or similar):

path = "C://Windows//Fonts"
# do some magic
dictionary = {
     'Arial':'Regular': 'Arial-Regular.ttf','Bold':'Arial-Bold.ttf',
     'Carlito:'Regular':' 8514fix.fon','Bold':'someweirdotherfile.fon'
}

The only things I got so far are the bare installed font names not their filenames. So if there is any way to either get the content as dictionary or to get the filename of the fonts please be so kind and give me a tip :)


Solution

  • To me, the best solution is to use DirecWrite which is the Windows API to manipulate fonts. It doesn'T involve using any hacks to get the information you need.

    There is many way to use DirectWrite to get the information you want, but here is the way that is compatible with the maximum version of Windows.

    Dependencies:

    import ctypes
    import json
    from pathlib import Path
    import time
    from comtypes import COMError, GUID, IUnknown, STDMETHOD
    from ctypes import WINFUNCTYPE, Structure, byref, c_ubyte, create_unicode_buffer, HRESULT, POINTER, windll, wintypes
    from dataclasses import asdict, dataclass
    from enum import IntEnum, IntFlag
    from sys import getwindowsversion
    from typing import Dict, List, Set
    
    
    @dataclass
    class FontFace:
        file_path: List[str] # See this link to know why we need a list: https://stackoverflow.com/questions/41161152/when-can-an-idwritefontface-have-more-than-one-file
        face_name: str
    
    
    @dataclass
    class FontFamily:
        family_name: str
        fonts: List[FontFace]
    
    
    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 DWRITE_FONT_SIMULATIONS(IntFlag):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/ne-dwrite-dwrite_font_simulations
        DWRITE_FONT_SIMULATIONS_NONE = 0x0000
        DWRITE_FONT_SIMULATIONS_BOLD = 0x0001
        DWRITE_FONT_SIMULATIONS_OBLIQUE = 0x0002
    
    class DWRITE_FONT_FAMILY_MODEL(IntEnum):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/ne-dwrite_3-dwrite_font_family_model
        DWRITE_FONT_FAMILY_MODEL_TYPOGRAPHIC = 0
        DWRITE_FONT_FAMILY_MODEL_WEIGHT_STRETCH_STYLE = 1
    
    
    class DWRITE_INFORMATIONAL_STRING_ID(IntEnum):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/ne-dwrite-dwrite_informational_string_id
        DWRITE_INFORMATIONAL_STRING_NONE = 0
        DWRITE_INFORMATIONAL_STRING_COPYRIGHT_NOTICE = 1
        DWRITE_INFORMATIONAL_STRING_VERSION_STRINGS = 2
        DWRITE_INFORMATIONAL_STRING_TRADEMARK = 3
        DWRITE_INFORMATIONAL_STRING_MANUFACTURER = 4
        DWRITE_INFORMATIONAL_STRING_DESIGNER = 5
        DWRITE_INFORMATIONAL_STRING_DESIGNER_URL = 6
        DWRITE_INFORMATIONAL_STRING_DESCRIPTION = 7
        DWRITE_INFORMATIONAL_STRING_FONT_VENDOR_URL = 8
        DWRITE_INFORMATIONAL_STRING_LICENSE_DESCRIPTION = 9
        DWRITE_INFORMATIONAL_STRING_LICENSE_INFO_URL = 10
        DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES = 11
        DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES = 12
        DWRITE_INFORMATIONAL_STRING_TYPOGRAPHIC_FAMILY_NAMES = 13
        DWRITE_INFORMATIONAL_STRING_TYPOGRAPHIC_SUBFAMILY_NAMES = 14
        DWRITE_INFORMATIONAL_STRING_SAMPLE_TEXT = 15
        DWRITE_INFORMATIONAL_STRING_FULL_NAME = 16
        DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME = 17
        DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_CID_NAME = 18
        DWRITE_INFORMATIONAL_STRING_WEIGHT_STRETCH_STYLE_FAMILY_NAME = 19
        DWRITE_INFORMATIONAL_STRING_DESIGN_SCRIPT_LANGUAGE_TAG = 20
        DWRITE_INFORMATIONAL_STRING_SUPPORTED_SCRIPT_LANGUAGE_TAG = 21
        DWRITE_INFORMATIONAL_STRING_PREFERRED_FAMILY_NAMES = DWRITE_INFORMATIONAL_STRING_TYPOGRAPHIC_FAMILY_NAMES
        DWRITE_INFORMATIONAL_STRING_PREFERRED_SUBFAMILY_NAMES = DWRITE_INFORMATIONAL_STRING_TYPOGRAPHIC_SUBFAMILY_NAMES
        DWRITE_INFORMATIONAL_STRING_WWS_FAMILY_NAME = DWRITE_INFORMATIONAL_STRING_WEIGHT_STRETCH_STYLE_FAMILY_NAME
    
    
    class IDWriteFontFileLoader(IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontfileloader
        _iid_ = GUID("{727cad4e-d6af-4c9e-8a08-d695b11caa49}")
        _methods_ = [
            STDMETHOD(None, "CreateStreamFromKey"),  # Need to be implemented
        ]
    
    
    class IDWriteLocalFontFileLoader(IDWriteFontFileLoader):
        # https://learn.microsoft.com/en-us/windows/win32/directwrite/idwritelocalfontfileloader
        _iid_ = GUID("{b2d9f3ec-c9fe-4a11-a2ec-d86208f7c0a2}")
        _methods_ = [
            STDMETHOD(HRESULT, "GetFilePathLengthFromKey", [wintypes.LPCVOID, wintypes.UINT, POINTER(wintypes.UINT)]),
            STDMETHOD(HRESULT, "GetFilePathFromKey", [wintypes.LPCVOID, wintypes.UINT, POINTER(wintypes.WCHAR), wintypes.UINT]),
            STDMETHOD(None, "GetLastWriteTimeFromKey"),  # Need to be implemented
        ]
    
    
    class IDWriteFontFile(IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontfile
        _iid_ = GUID("{739d886a-cef5-47dc-8769-1a8b41bebbb0}")
        _methods_ = [
            STDMETHOD(HRESULT, "GetReferenceKey", [POINTER(wintypes.LPCVOID), POINTER(wintypes.UINT)]),
            STDMETHOD(HRESULT, "GetLoader", [POINTER(POINTER(IDWriteFontFileLoader))]),
            STDMETHOD(HRESULT, "Analyze", [POINTER(wintypes.BOOL), POINTER(wintypes.UINT), POINTER(wintypes.UINT), POINTER(wintypes.UINT)]),
        ]
    
    
    class IDWriteLocalizedStrings(IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritelocalizedstrings
        _iid_ = GUID("{08256209-099a-4b34-b86d-c22b110e7771}")
        _methods_ = [
            STDMETHOD(wintypes.UINT, "GetCount"),
            STDMETHOD(HRESULT, "FindLocaleName", [POINTER(wintypes.WCHAR), POINTER(wintypes.UINT), POINTER(wintypes.BOOL)]),
            STDMETHOD(HRESULT, "GetLocaleNameLength", [wintypes.UINT, POINTER(wintypes.UINT)]),
            STDMETHOD(HRESULT, "GetLocaleName", [wintypes.UINT, POINTER(wintypes.WCHAR), wintypes.UINT]),
            STDMETHOD(HRESULT, "GetStringLength", [wintypes.UINT, POINTER(wintypes.UINT)]),
            STDMETHOD(HRESULT, "GetString", [wintypes.UINT, POINTER(wintypes.WCHAR), wintypes.UINT]),
        ]
    
    
    class IDWriteFontFace(IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontface
        _iid_ = GUID("{5f49804d-7024-4d43-bfa9-d25984f53849}")
        _methods_ = [
            STDMETHOD(None, "GetType"),  # Need to be implemented
            STDMETHOD(HRESULT, "GetFiles", [POINTER(wintypes.UINT), POINTER(POINTER(IDWriteFontFile))]),
            STDMETHOD(None, "GetIndex"),  # Need to be implemented
            STDMETHOD(wintypes.UINT, "GetSimulations"),
            STDMETHOD(None, "IsSymbolFont"),  # Need to be implemented
            STDMETHOD(None, "GetMetrics"),  # Need to be implemented
            STDMETHOD(None, "GetGlyphCount"),  # Need to be implemented
            STDMETHOD(None, "GetDesignGlyphMetrics"),  # Need to be implemented
            STDMETHOD(None, "GetGlyphIndices"),  # Need to be implemented
            STDMETHOD(None, "TryGetFontTable"),  # Need to be implemented
            STDMETHOD(None, "ReleaseFontTable"),  # Need to be implemented
            STDMETHOD(None, "GetGlyphRunOutline"),  # Need to be implemented
            STDMETHOD(None, "GetRecommendedRenderingMode"),  # Need to be implemented
            STDMETHOD(None, "GetGdiCompatibleMetrics"),  # Need to be implemented
            STDMETHOD(None, "GetGdiCompatibleGlyphMetrics"),  # Need to be implemented
        ]
    
    
    class IDWriteFont(IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefont
        _iid_ = GUID("{acd16696-8c14-4f5d-877e-fe3fc1d32737}")
    
    
    class IDWriteFontList(IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontlist
        _iid_ = GUID("{1a0d8438-1d97-4ec1-aef9-a2fb86ed6acb}")
        _methods_ = [
            STDMETHOD(None, "GetFontCollection"),  # Need to be implemented
            STDMETHOD(wintypes.UINT, "GetFontCount"),
            STDMETHOD(HRESULT, "GetFont", [wintypes.UINT, POINTER(POINTER(IDWriteFont))]),
        ]
    
    
    class IDWriteFontFamily(IDWriteFontList):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontfamily
        _iid_ = GUID("{da20d8ef-812a-4c43-9802-62ec4abd7add}")
        _methods_ = [
            STDMETHOD(HRESULT, "GetFamilyNames", [POINTER(POINTER(IDWriteLocalizedStrings))]),
            STDMETHOD(None, "GetFirstMatchingFont"),  # Need to be implemented
            STDMETHOD(None, "GetMatchingFonts"),  # Need to be implemented
        ]
    
    
    IDWriteFont._methods_ = [
            STDMETHOD(HRESULT, "GetFontFamily", [POINTER(POINTER(IDWriteFontFamily))]), 
            STDMETHOD(None, "GetWeight"),  # Need to be implemented
            STDMETHOD(None, "GetStretch"),  # Need to be implemented
            STDMETHOD(None, "GetStyle"),  # Need to be implemented
            STDMETHOD(None, "IsSymbolFont"),  # Need to be implemented
            STDMETHOD(HRESULT, "GetFaceNames", [POINTER(POINTER(IDWriteLocalizedStrings))]),
            STDMETHOD(HRESULT, "GetInformationalStrings", [wintypes.UINT, POINTER(POINTER(IDWriteLocalizedStrings)), POINTER(wintypes.BOOL)]),
            STDMETHOD(wintypes.UINT, "GetSimulations"),
            STDMETHOD(None, "GetMetrics"),  # Need to be implemented
            STDMETHOD(None, "HasCharacter"),  # Need to be implemented
            STDMETHOD(HRESULT, "CreateFontFace", [POINTER(POINTER(IDWriteFontFace))]),
        ]
    
    
    class IDWriteFontCollection(IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontcollection
        _iid_ = GUID("{a84cee02-3eea-4eee-a827-87c1a02a0fcc}")
        _methods_ = [
            STDMETHOD(wintypes.UINT, "GetFontFamilyCount"),
            STDMETHOD(HRESULT, "GetFontFamily", [wintypes.UINT, POINTER(POINTER(IDWriteFontFamily))]),
            STDMETHOD(None, "FindFamilyName"),  # Need to be implemented
            STDMETHOD(HRESULT, "GetFontFromFontFace", [POINTER(IDWriteFontFace), POINTER(POINTER(IDWriteFont))]),
        ]
    
    
    class IDWriteFontCollection1(IDWriteFontCollection):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontcollection1
        _iid_ = GUID("{53585141-D9F8-4095-8321-D73CF6BD116C}")
        _methods_ = [
            STDMETHOD(None, "GetFontSet1"),  # Need to be implemented
        ]
    
    
    class IDWriteFontCollection2(IDWriteFontCollection1):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontcollection2
        _iid_ = GUID("{514039C6-4617-4064-BF8B-92EA83E506E0}")
        _methods_ = [
            STDMETHOD(None, "GetFontFamily"),  # Need to be implemented
            STDMETHOD(None, "GetMatchingFonts"),  # Need to be implemented
            STDMETHOD(None, "GetFontFamilyModel"),  # Need to be implemented
            STDMETHOD(None, "GetFontSet2"),  # Need to be implemented
        ]
    
    
    class IDWriteFactory(IUnknown):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefactory
        _iid_ = GUID("{b859ee5a-d838-4b5b-a2e8-1adc7d93db48}")
        _methods_ = [
            STDMETHOD(HRESULT, "GetSystemFontCollection1", [POINTER(POINTER(IDWriteFontCollection)), wintypes.BOOL]),
            STDMETHOD(None, "CreateCustomFontCollection"),  # Need to be implemented
            STDMETHOD(None, "RegisterFontCollectionLoader"),  # Need to be implemented
            STDMETHOD(None, "UnregisterFontCollectionLoader"),  # Need to be implemented
            STDMETHOD(None, "CreateFontFileReference"),  # Need to be implemented
            STDMETHOD(None, "CreateCustomFontFileReference"),  # Need to be implemented
            STDMETHOD(None, "CreateFontFace"),  # Need to be implemented
            STDMETHOD(None, "CreateRenderingParams"),  # Need to be implemented
            STDMETHOD(None, "CreateMonitorRenderingParams"),  # Need to be implemented
            STDMETHOD(None, "CreateCustomRenderingParams"),  # Need to be implemented
            STDMETHOD(None, "RegisterFontFileLoader"),  # Need to be implemented
            STDMETHOD(None, "UnregisterFontFileLoader"),  # Need to be implemented
            STDMETHOD(None, "CreateTextFormat"),  # Need to be implemented
            STDMETHOD(None, "CreateTypography"),  # Need to be implemented
            STDMETHOD(None, "GetGdiInterop"),  # Need to be implemented
            STDMETHOD(None, "CreateTextLayout"),  # Need to be implemented
            STDMETHOD(None, "CreateGdiCompatibleTextLayout"),  # Need to be implemented
            STDMETHOD(None, "CreateEllipsisTrimmingSign"),  # Need to be implemented
            STDMETHOD(None, "CreateTextAnalyzer"),  # Need to be implemented
            STDMETHOD(None, "CreateNumberSubstitution"),  # Need to be implemented
            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_ = GUID("{30572f99-dac6-41db-a16e-0486307e606a}")
        _methods_ = [
            STDMETHOD(None, "GetEudcFontCollection"),  # Need to be implemented
            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_ = GUID("{0439fc60-ca44-4994-8dee-3a9af7b732ec}")
        _methods_ = [
            STDMETHOD(None, "GetSystemFontFallback"),  # Need to be implemented
            STDMETHOD(None, "CreateFontFallbackBuilder"),  # Need to be implemented
            STDMETHOD(None, "TranslateColorGlyphRun"),  # Need to be implemented
            STDMETHOD(None, "CreateCustomRenderingParams2"),  # Need to be implemented
            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_ = GUID("{9A1B41C3-D3BB-466A-87FC-FE67556A3B65}")
        _methods_ = [
            STDMETHOD(None, "CreateGlyphRunAnalysis"),  # Need to be implemented
            STDMETHOD(None, "CreateCustomRenderingParams3"),  # Need to be implemented
            STDMETHOD(None, "CreateFontFaceReference"),  # Need to be implemented
            STDMETHOD(None, "CreateFontFaceReference"),  # Need to be implemented
            STDMETHOD(None, "GetSystemFontSet1"),  # Need to be implemented
            STDMETHOD(None, "CreateFontSetBuilder"),  # Need to be implemented
            STDMETHOD(None, "CreateFontCollectionFromFontSet"),  # Need to be implemented
            STDMETHOD(HRESULT, "GetSystemFontCollection2", [wintypes.BOOL, POINTER(POINTER(IDWriteFontCollection1)), wintypes.BOOL]),
            STDMETHOD(None, "GetFontDownloadQueue"),  # Need to be implemented
        ]
    
    
    class IDWriteFactory4(IDWriteFactory3):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefactory4
        _iid_ = GUID("{4B0B5BD3-0797-4549-8AC5-FE915CC53856}")
        _methods_ = [
            STDMETHOD(None, "TranslateColorGlyphRun"),  # Need to be implemented
            STDMETHOD(None, "ComputeGlyphOrigins"),  # Need to be implemented
            STDMETHOD(None, "ComputeGlyphOrigins"),  # Need to be implemented
        ]
    
    
    class IDWriteFactory5(IDWriteFactory4):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefactory5
        _iid_ = GUID("{958DB99A-BE2A-4F09-AF7D-65189803D1D3}")
        _methods_ = [
            STDMETHOD(None, "CreateFontSetBuilder"),  # Need to be implemented
            STDMETHOD(None, "CreateInMemoryFontFileLoader"),  # Need to be implemented
            STDMETHOD(None, "CreateHttpFontFileLoader"),  # Need to be implemented
            STDMETHOD(None, "AnalyzeContainerType"),  # Need to be implemented
            STDMETHOD(None, "UnpackFontFile"),  # Need to be implemented
        ]
    
    
    class IDWriteFactory6(IDWriteFactory5):
        # https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefactory6
        _iid_ = GUID("{F3744D80-21F7-42EB-B35D-995BC72FC223}")
        _methods_ = [
            STDMETHOD(None, "CreateFontFaceReference"),  # Need to be implemented
            STDMETHOD(None, "CreateFontResource"),  # Need to be implemented
            STDMETHOD(None, "GetSystemFontSet2"),  # Need to be implemented
            STDMETHOD(HRESULT, "GetSystemFontCollection3", [wintypes.BOOL, wintypes.UINT, POINTER(POINTER(IDWriteFontCollection2))]),
            STDMETHOD(None, "CreateFontCollectionFromFontSet"),  # Need to be implemented
            STDMETHOD(None, "CreateFontSetBuilder"),  # Need to be implemented
            STDMETHOD(None, "CreateTextFormat"),  # Need to be implemented
        ]
    
    
    class Kernel32:
        def __init__(self):
            kernel32 = windll.kernel32
    
            # https://learn.microsoft.com/en-us/windows/win32/api/winnls/nf-winnls-getuserdefaultlocalename
            self.GetUserDefaultLocaleName = kernel32.GetUserDefaultLocaleName
            self.GetUserDefaultLocaleName.restype = wintypes.INT
            self.GetUserDefaultLocaleName.argtypes = [wintypes.LPWSTR, wintypes.INT]
            self.GetUserDefaultLocaleName.errcheck = self.errcheck_is_result_0
    
            self.LOCALE_NAME_MAX_LENGTH = 85
    
    
        def GetUserDefaultLocaleNameFunc(self) -> str:
            locale_buffer = create_unicode_buffer(self.LOCALE_NAME_MAX_LENGTH)
            try:
                self.GetUserDefaultLocaleName(locale_buffer, self.LOCALE_NAME_MAX_LENGTH)
            except ValueError:
                return "en-us"
    
            return locale_buffer.value
    
    
        @staticmethod
        def errcheck_is_result_0(result, func, args):
            if result == 0:
                raise ValueError(f"Error encountered with {func.__name__}.")
            return result
    
    
    class DirectWrite:
        def __init__(self):
            dwrite = windll.dwrite
    
            # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-dwritecreatefactory
            self.DWriteCreateFactory = dwrite.DWriteCreateFactory
            self.DWriteCreateFactory.restype = HRESULT
            self.DWriteCreateFactory.argtypes = [wintypes.UINT, GUID, POINTER(POINTER(IUnknown))]
    
    
        def get_str_from_localized_strings(self, localized_strings) -> str:
            # From https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritelocalizedstrings#remarks
    
            kernel32 = Kernel32()
            locale_name = kernel32.GetUserDefaultLocaleNameFunc()
    
            index = wintypes.UINT()
            exists = wintypes.BOOL()
            localized_strings.FindLocaleName(locale_name, byref(index), byref(exists))
    
            if not exists:
                localized_strings.FindLocaleName("en-us", byref(index), byref(exists))
    
            if not exists:
                index = 0
    
            length = wintypes.UINT()
            localized_strings.GetStringLength(index, byref(length))
    
            localized_strings_buffer = create_unicode_buffer(length.value + 1)
            localized_strings.GetString(index, localized_strings_buffer, len(localized_strings_buffer))
    
            return localized_strings_buffer.value
    
    
    class WindowsVersionHelpers:
        @staticmethod
        def is_windows_version_or_greater(windows_version, major: int, minor: int, build: int) -> bool:
            """
            Parameters:
                windows_version: An object from getwindowsversion.
                major (int): The minimum major OS version number.
                minor (int): The minimum minor OS version number.
                build (int): The minimum build 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.build >= build
                )
    
        @staticmethod
        def is_windows_vista_sp2_or_greater(windows_version) -> bool:
            # From https://www.lifewire.com/windows-version-numbers-2625171
            return WindowsVersionHelpers.is_windows_version_or_greater(windows_version, 6, 0, 6002)
        
        @staticmethod
        def is_windows_fall_creators_update_or_greater(windows_version) -> bool:
            # From https://www.lifewire.com/windows-version-numbers-2625171
            return WindowsVersionHelpers.is_windows_version_or_greater(windows_version, 10, 0, 16299)
    
    
    def get_file_path_from_IDWriteFontFace(font_face: POINTER(IDWriteFontFace)) -> List[str]:
        face_file_path = []
    
        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))
    
            local_loader = loader.QueryInterface(IDWriteLocalFontFileLoader)
    
            is_supported_font_type = wintypes.BOOL()
            font_file_type = wintypes.UINT()
            font_face_type = wintypes.UINT()
            number_of_faces = wintypes.UINT()
            font_file.Analyze(byref(is_supported_font_type), byref(font_file_type), byref(font_face_type), byref(number_of_faces))
    
            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))
    
            face_file_path.append(str(Path(buffer.value).resolve()))
        
        return face_file_path
    
    
    def get_system_fonts_families_simulate_gdi() -> List[FontFamily]:
        """
        It use the [RBIZ font family model](https://learn.microsoft.com/en-us/windows/win32/directwrite/font-selection#rbiz-font-family-model)
        This method only simulates how GDI works. In some case, the result may be different from GDI.
        """
        windows_version = getwindowsversion()
    
        if not WindowsVersionHelpers.is_windows_vista_sp2_or_greater(windows_version):
            raise OSError("This program only works on Windows Vista SP2 or more")
    
        dwrite = DirectWrite()
        fonts_families: Dict[str, FontFamily] = {}
    
        dwrite_factory = POINTER(IDWriteFactory)()
        dwrite.DWriteCreateFactory(DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_ISOLATED, dwrite_factory._iid_, byref(dwrite_factory))
    
        sys_collection = POINTER(IDWriteFontCollection)()
        dwrite_factory.GetSystemFontCollection1(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()):
                try:
                    font = POINTER(IDWriteFont)()
                    family.GetFont(j, byref(font))
                except COMError:
                    # If the file doesn't exist, DirectWrite raise an exception
                    continue
    
                simulations = font.GetSimulations()
                if simulations != DWRITE_FONT_SIMULATIONS.DWRITE_FONT_SIMULATIONS_NONE:
                    continue
    
                family_names = POINTER(IDWriteLocalizedStrings)()
                exists = wintypes.BOOL()
                font.GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_ID.DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES, byref(family_names), byref(exists))
                family_names_str = dwrite.get_str_from_localized_strings(family_names)
                if not exists:
                    raise ValueError("Could not fetch the family names")
    
                if family_names_str in fonts_families:
                    font_familie = fonts_families[family_names_str]
                else:
                    font_familie = FontFamily(family_names_str, [])
                    fonts_families[family_names_str] = font_familie
    
                face_name = POINTER(IDWriteLocalizedStrings)()
                exists = wintypes.BOOL()
                font.GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_ID.DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES, byref(face_name), byref(exists))
                face_name_str = dwrite.get_str_from_localized_strings(face_name)
                if not exists:
                    raise ValueError("Could not fetch the family names")
                
                font_face = POINTER(IDWriteFontFace)()
                font.CreateFontFace(byref(font_face))
    
                face_file_path = get_file_path_from_IDWriteFontFace(font_face)
    
                font_familie.fonts.append(FontFace(face_file_path, face_name_str))
        return fonts_families.values()
    
    
    def get_system_fonts_families_WWS() -> List[FontFamily]:
        """
        It use the [Weight-stretch-style font family model](https://learn.microsoft.com/en-us/windows/win32/directwrite/font-selection#weight-stretch-style-font-family-model)
        """
        windows_version = getwindowsversion()
    
        if not WindowsVersionHelpers.is_windows_vista_sp2_or_greater(windows_version):
            raise OSError("This program only works on Windows Vista SP2 or more")
    
        dwrite = DirectWrite()
        fonts_families: List[FontFamily] = []
    
        dwrite_factory = POINTER(IDWriteFactory)()
        dwrite.DWriteCreateFactory(DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_ISOLATED, dwrite_factory._iid_, byref(dwrite_factory))
    
        sys_collection = POINTER(IDWriteFontCollection)()
        dwrite_factory.GetSystemFontCollection1(byref(sys_collection), False)
    
        for i in range(sys_collection.GetFontFamilyCount()):
            family = POINTER(IDWriteFontFamily)()
            sys_collection.GetFontFamily(i, byref(family))
    
            family_names = POINTER(IDWriteLocalizedStrings)()
            family.GetFamilyNames(byref(family_names))
            family_names_str = dwrite.get_str_from_localized_strings(family_names)
    
            faces: List[FontFace] = []
            for j in range(family.GetFontCount()):
                try:
                    font = POINTER(IDWriteFont)()
                    family.GetFont(j, byref(font))
                except COMError:
                    # If the file doesn't exist, DirectWrite raise an exception
                    continue
    
                simulations = font.GetSimulations()
                if simulations != DWRITE_FONT_SIMULATIONS.DWRITE_FONT_SIMULATIONS_NONE:
                    continue
    
                face_name = POINTER(IDWriteLocalizedStrings)()
                font.GetFaceNames(byref(face_name))
                face_name_str = dwrite.get_str_from_localized_strings(face_name)
    
                font_face = POINTER(IDWriteFontFace)()
                font.CreateFontFace(byref(font_face))
    
                face_file_path = get_file_path_from_IDWriteFontFace(font_face)
    
                faces.append(FontFace(face_file_path, face_name_str))
            fonts_families.append(FontFamily(family_names_str, faces))
        return fonts_families
    
    
    def get_system_fonts_families_typographic_model() -> List[FontFamily]:
        """
        It use the [Typographic font family model](https://learn.microsoft.com/en-us/windows/win32/directwrite/font-selection#typographic-font-family-model)
        """
        windows_version = getwindowsversion()
    
        if not WindowsVersionHelpers.is_windows_fall_creators_update_or_greater(windows_version):
            raise OSError("This program only works on Windows 10 Fall Creators Update or more")
    
        dwrite = DirectWrite()
        fonts_families: List[FontFamily] = []
    
        dwrite_factory = POINTER(IDWriteFactory6)()
        dwrite.DWriteCreateFactory(DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_ISOLATED, dwrite_factory._iid_, byref(dwrite_factory))
    
        sys_collection = POINTER(IDWriteFontCollection2)()
        dwrite_factory.GetSystemFontCollection3(False, DWRITE_FONT_FAMILY_MODEL.DWRITE_FONT_FAMILY_MODEL_TYPOGRAPHIC, byref(sys_collection))
    
        for i in range(sys_collection.GetFontFamilyCount()):
            family = POINTER(IDWriteFontFamily)()
            sys_collection.GetFontFamily(i, byref(family))
    
            family_names = POINTER(IDWriteLocalizedStrings)()
            family.GetFamilyNames(byref(family_names))
            family_names_str = dwrite.get_str_from_localized_strings(family_names)
    
            faces: List[FontFace] = []
            for j in range(family.GetFontCount()):
                try:
                    font = POINTER(IDWriteFont)()
                    family.GetFont(j, byref(font))
                except COMError:
                    # If the file doesn't exist, DirectWrite raise an exception
                    continue
    
                simulations = font.GetSimulations()
                if simulations != DWRITE_FONT_SIMULATIONS.DWRITE_FONT_SIMULATIONS_NONE:
                    continue
    
                face_file_path: List[Path] = []
    
                face_name = POINTER(IDWriteLocalizedStrings)()
                font.GetFaceNames(byref(face_name))
                face_name_str = dwrite.get_str_from_localized_strings(face_name)
    
                font_face = POINTER(IDWriteFontFace)()
                font.CreateFontFace(byref(font_face))
    
                face_file_path = get_file_path_from_IDWriteFontFace(font_face)
    
                faces.append(FontFace(face_file_path, face_name_str))
            fonts_families.append(FontFamily(family_names_str, faces))
        return fonts_families
    
    
    def main():
        # use get_system_fonts_families_simulate_gdi, get_system_fonts_families_WWS or get_system_fonts_families_typographic_model
        font_families = get_system_fonts_families_simulate_gdi() 
    
        font_families_dict = [asdict(font_family) for font_family in get_system_fonts_families_simulate_gdi()]
        json_string = json.dumps(font_families_dict, indent=4)
        print(json_string)
    
    if __name__ == "__main__":
        main()