Repository: moi15moi/FindSystemFontsFilename (language: python)

Issue:
Fonts activated by FontBase are not found
Reopening https://github.com/moi15moi/FontCollector/issues/40 here

> (Windows 11)
> 
> The current font enumeration method does not find fonts activated by [FontBase](https://fontba.se/); even though they should be detected as system fonts considering every app with a font picker can find them.
> 
> Potentially could also be causing issues with other apps that utilize the same method as FontBase to activate their fonts.
> 
> I've confirmed with other users that they also experience the same issue. One user have testified that FontCollector successfully finds their FontBase fonts though but we couldn't identify the cause—so there might be a configuration change necessary for it to work.
[Windows] Always get cached font
Fix #14
I have also made _get_fonts_filename_windows_vista_sp2_or_more a bit faster by ignoring the simulated font.

Special thanks to @yuv420p10le. He is the one who remarks that when the checkForUpdates parameter of  [IDWriteFactory::GetSystemFontCollection](https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefactory-getsystemfontcollection) is true, it always collects the fonts activated by FontBase (previously, depending of the computer, it wasn't always getting those fonts)


Discussion / hints:
Thanks for opening an issue.
I will need your help to debug this problem because I am not able to reproduce the problem.

On the issue you opened in FontCollector, you said:
> The fonts can be found at the registry key: ``HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Fonts``

There are many reasons why we can't use this registry key to get the fonts. One of them is that it doesn't collect system fonts like ``Marlett``.

I have some questions for you:
1. Do you see the activated font in the registry?
2. Have you rebooted? In some very specific moments, DirectWrite uses cache and doesn't refresh it, which can miss some activated fonts. This is a bug that I can do nothing to correct it.
3. What is the file path of the activated font by FontBase? The font should be in ``%USERPROFILE%\FontBase``.
> Do you see the activated font in the registry?

Yes

> Have you rebooted? In some very specific moments, DirectWrite uses cache and doesn't refresh it, which can miss some activated fonts. This is a bug that I can do nothing to correct it.

I haven't. I can try; am I supposed to reboot _after_ enabling fonts?
Maybe there's a different _working_ way to find these fonts? I assume there is, because other software with font pickers e.g. Aegisub are able to display them perfectly fine.

> What is the file path of the activated font by FontBase? The font should be in %USERPROFILE%\FontBase.

You're correct, it's in that directory

```
C:\Users\yuv420p10le\FontBase\GANDHISANS-BOLD.OTF
```
> I haven't. I can try; am I supposed to reboot _after_ enabling fonts?

Exactly. Reboot and reactivate it.

> Maybe there's a different _working_ way to find these fonts? I assume there is, because other software with font pickers e.g. Aegisub are able to display them perfectly fine.

Aegisub and any other tools use GDI and/or DirectWrite which is also what this tool use. There is no reason that this tool doesn't find it.

Could you tell me which version of FindSystemFontsFilename you have?
You can get it with ``pip show FindSystemFontsFilename``. If it is below 0.2.0, update the package with ``pip install FindSystemFontsFilename -U`` and run this
```py
from find_system_fonts_filename import get_system_fonts_filename

fonts = get_system_fonts_filename()

for font in fonts:
    if font.lower() == r'C:\Users\yuv420p10le\FontBase\GANDHISANS-BOLD.OTF'.lower():
        print(f"Found at {font}")
```
Rebooted, activated fonts. Ensured I'm on 2.0.

The snippet does not reach the print statement either when the font is activated or not.
Finally, I am able to reproduce the issue. I am using a VM that is on 10.0.22631.2861.
This issue seems to only happen on Windows 11 which is a bit strange since there isn't any big update that only impacts Windows 11 (information from [here](https://learn.microsoft.com/en-us/windows/win32/directwrite/what-s-new-in-directwrite-for-windows-8-consumer-preview)).

> One user have testified that FontCollector successfully finds their FontBase fonts though but we couldn't identify the cause

~~Does this user is on Windows 10? For me, it works fine on Windows 10.~~
The user is on Windows 11. Specifically 10.0.22631.3235
@yuv420p10le Could you confirm me that #15 fix your issue?
The issue is resolved on fontcollector v3.0.0rc2 with #15 applied so I'm gonna say yes
That doesn't make any sense.
Anyways, since now it also collect the fonts added with AddFontRessource, everything should be alright.
I just realize with this new method, when a user call [AddFontResource](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-addfontresourcew), the font isn't collected. Before this PR, on windows 10 or more, the font installed via [AddFontResource](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-addfontresourcew) where collected.

This is a big concern, so I cannot accept this PR.

Edit 1: It is really strange because if I set checkForUpdates to false of [IDWriteFactory3::GetSystemFontCollection](https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/nf-dwrite_3-idwritefactory3-getsystemfontcollection), it collect the font added by [AddFontResource](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-addfontresourcew).
But, to collect the font installed by fontbase (see note 1), I need to set checkForUpdates to true.


Note 1: In general, without this PR, it already collect the font installed by fontbase, but when I installed windows 11 in a VM, it doesn't collect them.

Fix patch:
diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml
index 557bd77..cfb4319 100644
--- a/.github/workflows/publish_to_pypi.yml
+++ b/.github/workflows/publish_to_pypi.yml
@@ -1,8 +1,8 @@
 name: Publish Package to PyPI
 
 on:
-    release:
-      types: [published]
+  release:
+    types: [published]
 
 jobs:
   publish-to-pypi:
@@ -10,21 +10,21 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - name: Checkout code
-      uses: actions/checkout@master
+      uses: actions/checkout@4
 
     - name: Set up Python 3.10
-      uses: actions/setup-python@v3
+      uses: actions/setup-python@v5
       with:
-        python-version: '3.10'
+        python-version: '3.12'
 
-    - name: Install wheel
+    - name: "Install build"
       run: >-
-        pip install wheel
+        pip install build
 
-    - name: Create dist
+    - name: Build package
       run: >-
-        python3 setup.py sdist bdist_wheel
-
+        python -m build
+  
     - name: Publish distribution to PyPI
       uses: pypa/gh-action-pypi-publish@master
       with:
diff --git a/find_system_fonts_filename/__init__.py b/find_system_fonts_filename/__init__.py
index 66b99b6..7b687c2 100644
--- a/find_system_fonts_filename/__init__.py
+++ b/find_system_fonts_filename/__init__.py
@@ -1,4 +1,4 @@
-from .fonts_filename import get_system_fonts_filename
-from .exceptions import AndroidLibraryNotFound, FindSystemFontsFilenameException, FontConfigNotFound, OSNotSupported
+from .fonts_filename import *
+from .exceptions import *
 
 __version__ = "0.2.0"
diff --git a/find_system_fonts_filename/android/__init__.py b/find_system_fonts_filename/android/__init__.py
new file mode 100644
index 0000000..6c22e6e
--- /dev/null
+++ b/find_system_fonts_filename/android/__init__.py
@@ -0,0 +1,1 @@
+from .android_fonts import *
diff --git a/find_system_fonts_filename/android/android.py b/find_system_fonts_filename/android/android.py
new file mode 100644
index 0000000..00ab05d
--- /dev/null
+++ b/find_system_fonts_filename/android/android.py
@@ -0,0 +1,43 @@
+from ctypes import c_char_p, c_void_p, cdll, util
+from ..exceptions import AndroidLibraryNotFound, OSNotSupported
+
+__all__ = ["Android"]
+
+
+class Android():
+    def __init__(self) -> None:
+
+        android_library_name = util.find_library("android")
+
+        if android_library_name is None:
+            raise AndroidLibraryNotFound("You need to have the libandroid library. It is only available since the SDK/API level 29.")
+
+        android = cdll.LoadLibrary(android_library_name)
+
+        try:
+            # https://developer.android.com/ndk/reference/group/font#asystemfontiterator_open
+            self.ASystemFontIterator_open = android.ASystemFontIterator_open
+            self.ASystemFontIterator_open.restype = c_void_p
+            self.ASystemFontIterator_open.argtypes = []
+        except AttributeError:
+            raise OSNotSupported("FindSystemFontsFilename only works on Android API level 29 or more.")
+
+        # https://developer.android.com/ndk/reference/group/font#asystemfontiterator_next
+        self.ASystemFontIterator_next = android.ASystemFontIterator_next
+        self.ASystemFontIterator_next.restype = c_void_p
+        self.ASystemFontIterator_next.argtypes = [c_void_p]
+
+        # https://developer.android.com/ndk/reference/group/font#asystemfontiterator_close
+        self.ASystemFontIterator_close = android.ASystemFontIterator_close
+        self.ASystemFontIterator_close.restype = None
+        self.ASystemFontIterator_close.argtypes = [c_void_p]
+
+        # https://developer.android.com/ndk/reference/group/font#afont_getfontfilepath
+        self.AFont_getFontFilePath = android.AFont_getFontFilePath
+        self.AFont_getFontFilePath.restype = c_char_p
+        self.AFont_getFontFilePath.argtypes = [c_void_p]
+
+        # https://developer.android.com/ndk/reference/group/font#afont_close
+        self.AFont_close = android.AFont_close
+        self.AFont_close.restype = None
+        self.AFont_close.argtypes = [c_void_p]
diff --git a/find_system_fonts_filename/android/android_fonts.py b/find_system_fonts_filename/android/android_fonts.py
new file mode 100644
index 0000000..c569c8b
--- /dev/null
+++ b/find_system_fonts_filename/android/android_fonts.py
@@ -0,0 +1,67 @@
+from pathlib import Path
+from .android import Android
+from contextlib import contextmanager
+from os import close, devnull, dup, dup2, O_WRONLY, open
+from sys import stderr, stdout
+from typing import Set
+from ..exceptions import OSNotSupported
+from ..system_fonts import SystemFonts
+
+__all__ = ["AndroidFonts"]
+
+
+class AndroidFonts(SystemFonts):
+
+    def get_system_fonts_filename() -> Set[str]:
+        android = Android()
+        fonts_filename = set()
+
+        # Redirect the stderr_and_stdout to null since we don't care about android logs
+        # There is __android_log_set_minimum_priority to set the log level, but even if I set it to 8 (which correspond to ANDROID_LOG_SILENT),
+        #   it was still logging: https://developer.android.com/ndk/reference/group/logging#__android_log_set_minimum_priority
+        with AndroidFonts._silence_stderr_and_stdout():
+            font_iterator = android.ASystemFontIterator_open()
+
+            while True:
+                font = android.ASystemFontIterator_next(font_iterator)
+
+                if font is None:
+                    break
+
+                font_filename = android.AFont_getFontFilePath(font).decode("utf-8")
+                fonts_filename.add(font_filename)
+
+                android.AFont_close(font)
+            android.ASystemFontIterator_close(font_iterator)
+
+        return fonts_filename
+
+
+    def install_font(font_filename: Path, windows_flags: bool) -> None:
+        raise OSNotSupported("You cannot install font on android.")
+
+
+    def uninstall_font(font_filename: Path, windows_flags: bool) -> None:
+        raise OSNotSupported("You cannot uninstall font on android.")
+
+
+    @contextmanager
+    def _silence_stderr_and_stdout():
+        # From: https://stackoverflow.com/a/75037627/15835974
+        stderr_fd = stderr.fileno()
+        orig_stderr_fd = dup(stderr_fd)
+
+        stdout_fd = stdout.fileno()
+        orig_stdout_fd = dup(stdout_fd)
+
+        null_fd = open(devnull, O_WRONLY)
+        dup2(null_fd, stderr_fd)
+        dup2(null_fd, stdout_fd)
+        try:
+            yield
+        finally:
+            dup2(orig_stderr_fd, stderr_fd)
+            dup2(orig_stdout_fd, stdout_fd)
+            close(orig_stderr_fd)
+            close(orig_stdout_fd)
+            close(null_fd)
diff --git a/find_system_fonts_filename/android_fonts.py b/find_system_fonts_filename/android_fonts.py
deleted file mode 100644
index 7b3c87d..0000000
--- a/find_system_fonts_filename/android_fonts.py
+++ /dev/null
@@ -1,93 +0,0 @@
-from contextlib import contextmanager
-from ctypes import c_char_p, c_void_p, cdll, util
-from os import close, devnull, dup, dup2, O_WRONLY, open
-from sys import stderr, stdout
-from typing import Set
-from .exceptions import AndroidLibraryNotFound, OSNotSupported
-from .system_fonts import SystemFonts
-
-
-class AndroidFonts(SystemFonts):
-    _android = None
-
-    def get_system_fonts_filename() -> Set[str]:
-        if AndroidFonts._android is None:
-             AndroidFonts._load_android_library()
-
-        fonts_filename = set()
-
-        # Redirect the stderr_and_stdout to null since we don't care about android logs
-        # There is __android_log_set_minimum_priority to set the log level, but even if I set it to 8 (which correspond to ANDROID_LOG_SILENT),
-        #   it was still logging: https://developer.android.com/ndk/reference/group/logging#__android_log_set_minimum_priority
-        with AndroidFonts._silence_stderr_and_stdout():
-            font_iterator = AndroidFonts._android.ASystemFontIterator_open()
-
-            while True:
-                font = AndroidFonts._android.ASystemFontIterator_next(font_iterator)
-
-                if font is None:
-                    break
-
-                font_filename = AndroidFonts._android.AFont_getFontFilePath(font).decode("utf-8")
-                fonts_filename.add(font_filename)
-
-                AndroidFonts._android.AFont_close(font)
-            AndroidFonts._android.ASystemFontIterator_close(font_iterator)
-
-        return fonts_filename
-
-    @staticmethod
-    def _load_android_library():
-        android_library_name = util.find_library("android")
-
-        if android_library_name is None:
-            raise AndroidLibraryNotFound("You need to have the libandroid library. It is only available since the SDK/API level 29.")
-
-        AndroidFonts._android = cdll.LoadLibrary(android_library_name)
-
-        # The android device need to be at least on the level 29.
-        # The function android_get_device_api_level is only available since the level 29, so we can't use it. See: https://developer.android.com/ndk/reference/group/apilevels#android_get_device_api_level
-        # So, we try and see if the function are available
-        try:
-            # https://developer.android.com/ndk/reference/group/font#asystemfontiterator_open
-            AndroidFonts._android.ASystemFontIterator_open.restype = c_void_p
-            AndroidFonts._android.ASystemFontIterator_open.argtypes = []
-        except AttributeError:
-            raise OSNotSupported("FindSystemFontsFilename only works on Android API level 29 or more.")
-
-        # https://developer.android.com/ndk/reference/group/font#asystemfontiterator_next
-        AndroidFonts._android.ASystemFontIterator_next.restype = c_void_p
-        AndroidFonts._android.ASystemFontIterator_next.argtypes = [c_void_p]
-
-        # https://developer.android.com/ndk/reference/group/font#asystemfontiterator_close
-        AndroidFonts._android.ASystemFontIterator_close.restype = None
-        AndroidFonts._android.ASystemFontIterator_close.argtypes = [c_void_p]
-
-        # https://developer.android.com/ndk/reference/group/font#afont_getfontfilepath
-        AndroidFonts._android.AFont_getFontFilePath.restype = c_char_p
-        AndroidFonts._android.AFont_getFontFilePath.argtypes = [c_void_p]
-
-        # https://developer.android.com/ndk/reference/group/font#afont_close
-        AndroidFonts._android.AFont_close.restype = None
-        AndroidFonts._android.AFont_close.argtypes = [c_void_p]
-
-    @contextmanager
-    def _silence_stderr_and_stdout():
-        # From: https://stackoverflow.com/a/75037627/15835974
-        stderr_fd = stderr.fileno()
-        orig_stderr_fd = dup(stderr_fd)
-
-        stdout_fd = stdout.fileno()
-        orig_stdout_fd = dup(stdout_fd)
-
-        null_fd = open(devnull, O_WRONLY)
-        dup2(null_fd, stderr_fd)
-        dup2(null_fd, stdout_fd)
-        try:
-            yield
-        finally:
-            dup2(orig_stderr_fd, stderr_fd)
-            dup2(orig_stdout_fd, stdout_fd)
-            close(orig_stderr_fd)
-            close(orig_stdout_fd)
-            close(null_fd)
diff --git a/find_system_fonts_filename/exceptions.py b/find_system_fonts_filename/exceptions.py
index f2065ef..82850f2 100644
--- a/find_system_fonts_filename/exceptions.py
+++ b/find_system_fonts_filename/exceptions.py
@@ -1,3 +1,13 @@
+__all__ = [
+    "FindSystemFontsFilenameException",
+    "OSNotSupported",
+    "FontConfigNotFound",
+    "AndroidLibraryNotFound",
+    "NotSupportedFontFile",
+    "SystemApiError"
+]
+
+
 class FindSystemFontsFilenameException(Exception):
     "Basic exception for this package"
     pass
@@ -16,3 +26,11 @@ class FontConfigNotFound(FindSystemFontsFilenameException):
 class AndroidLibraryNotFound(FindSystemFontsFilenameException):
     "Raised when the android library haven't been found"
     pass
+
+class NotSupportedFontFile(FindSystemFontsFilenameException):
+    "Raised when the user try to install a font, but it isn't supported by the OS"
+    pass
+
+class SystemApiError(FindSystemFontsFilenameException):
+    "Raised when the system API returned an unexpected error."
+    pass
diff --git a/find_system_fonts_filename/fonts_filename.py b/find_system_fonts_filename/fonts_filename.py
index 336536c..b38482c 100644
--- a/find_system_fonts_filename/fonts_filename.py
+++ b/find_system_fonts_filename/fonts_filename.py
@@ -1,28 +1,70 @@
+import sys
+from pathlib import Path
 from platform import system
-from os import environ
 from typing import Set
 from .exceptions import OSNotSupported
+from .system_fonts import SystemFonts
 
+__all__ = [
+    "get_system_fonts_filename",
+    "install_font",
+    "uninstall_font",
+]
 
-def get_system_fonts_filename() -> Set[str]:
+
+def get_system_fonts_class() -> SystemFonts:
     system_name = system()
 
     if system_name == "Windows":
-        from .windows_fonts import WindowsFonts
-        return WindowsFonts.get_system_fonts_filename()
+        from .windows import WindowsFonts
+        return WindowsFonts
 
     elif system_name == "Linux":
-        # ANDROID_ROOT or ANDROID_BOOTLOGO - https://stackoverflow.com/a/66174754/15835974
-        if any(t in environ.keys() for t in ("ANDROID_ROOT", "ANDROID_BOOTLOGO")):
-            from .android_fonts import AndroidFonts
-            return AndroidFonts.get_system_fonts_filename()
+        if hasattr(sys, "getandroidapilevel"):
+            from .android import AndroidFonts
+            return AndroidFonts
         else:
-            from .linux_fonts import LinuxFonts
-            return LinuxFonts.get_system_fonts_filename()
+            from .linux import LinuxFonts
+            return LinuxFonts
 
     elif system_name == "Darwin":
-        from .mac_fonts import MacFonts
-        return MacFonts.get_system_fonts_filename()
+        from .mac import MacFonts
+        return MacFonts
 
     else:
-        raise OSNotSupported("FindSystemFontsFilename only works on Windows, Mac and Linux.")
+        raise OSNotSupported("FindSystemFontsFilename only works on Windows, Mac, Linux and Android.")
+
+
+def get_system_fonts_filename() -> Set[str]:
+    return get_system_fonts_class().get_system_fonts_filename()
+
+
+def install_font(font_filename: Path, add_font_to_registry: bool = False) -> None:
+    """Install a font from its filename
+
+    Args:
+        add_font_to_registry: It will add the font in the Windows Registry.
+            This argument is Windows Only.
+            It adds the font to the Windows Registry only if the Windows version is 10.0.17083 (also known as version 1803) or later.
+            Prior to this version, Windows did not support font registration in the registry.
+    """
+    if not font_filename.is_file():
+        raise FileNotFoundError(f"The file \"{font_filename}\" doesn't exist")
+
+    return get_system_fonts_class().install_font(font_filename, add_font_to_registry)
+
+
+def uninstall_font(font_filename: Path, remove_font_in_registry: bool = False) -> None:
+    """Uninstall a font from its filename
+
+    Args:
+        remove_font_in_registry: It will also remove the Windows Registry entry that has been added
+            when install_font with add_font_to_registry set to True has been called.
+            This argument is Windows Only.
+            It adds the font to the Windows Registry only if the Windows version is 10.0.17083 (also known as version 1803) or later.
+            Prior to this version, Windows did not support font registration in the registry.
+    """
+    if not font_filename.is_file():
+        raise FileNotFoundError(f"The file \"{font_filename}\" doesn't exist")
+
+    return get_system_fonts_class().uninstall_font(font_filename, remove_font_in_registry)
diff --git a/find_system_fonts_filename/linux/__init__.py b/find_system_fonts_filename/linux/__init__.py
new file mode 100644
index 0000000..6511e93
--- /dev/null
+++ b/find_system_fonts_filename/linux/__init__.py
@@ -0,0 +1,1 @@
+from .linux_fonts import *
diff --git a/find_system_fonts_filename/linux/fontconfig.py b/find_system_fonts_filename/linux/fontconfig.py
new file mode 100644
index 0000000..7aa8360
--- /dev/null
+++ b/find_system_fonts_filename/linux/fontconfig.py
@@ -0,0 +1,145 @@
+from ctypes import c_char_p, c_int, c_void_p, cdll, POINTER, Structure, util
+from enum import Enum, IntEnum
+from ..exceptions import FontConfigNotFound
+
+__all__ = [
+    "FontConfig",
+    "FC_FONT_FORMAT",
+    "FC_RESULT",
+    "FcFontSet"
+]
+
+
+class FC_FONT_FORMAT(Enum):
+    # https://freetype.org/freetype2/docs/reference/ft2-font_formats.html#ft_get_font_format
+    # https://gitlab.freedesktop.org/freetype/freetype/-/blob/0a3836c97d5e84d6721ac0fd2839e8ae1b7be8d9/include/freetype/internal/services/svfntfmt.h#L36
+    FT_FONT_FORMAT_TRUETYPE = b"TrueType"
+    FT_FONT_FORMAT_TYPE_1 = b"Type 1"
+    FT_FONT_FORMAT_BDF = b"BDF"
+    FT_FONT_FORMAT_PCF = b"PCF"
+    FT_FONT_FORMAT_TYPE_42 = b"Type 42"
+    FT_FONT_FORMAT_CID = b"CID Type 1"
+    FT_FONT_FORMAT_CFF = b"CFF"
+    FT_FONT_FORMAT_PFR = b"PFR"
+    FT_FONT_FORMAT_WINFNT = b"Windows FNT"
+
+
+class FC_RESULT(IntEnum):
+    # https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/222d058525506e587a45368f10e45e4b80ca541f/fontconfig/fontconfig.h#L241
+    FC_RESULT_MATCH = 0
+    FC_RESULT_NO_MATCH = 1
+    FC_RESULT_TYPE_MISMATCH = 2
+    FC_RESULT_NO_ID = 3
+    FC_RESULT_OUT_OF_MEMORY = 4
+
+
+class FcFontSet(Structure):
+    # https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/222d058525506e587a45368f10e45e4b80ca541f/fontconfig/fontconfig.h#L278
+    _fields_ = [
+        ("nfont", c_int),
+        ("sfont", c_int),
+        ("fonts", POINTER(POINTER(c_void_p))),
+    ]
+
+
+class FontConfig():
+    def __init__(self) -> None:
+
+        font_config_library_name = util.find_library("fontconfig")
+
+        if font_config_library_name is None:
+            raise FontConfigNotFound("You need to install FontConfig to get the fonts filename")
+
+        font_config = cdll.LoadLibrary(font_config_library_name)
+
+        self.FC_FONTFORMAT = FontConfig.string_to_cstring("fontformat")
+        self.FC_FILE = FontConfig.string_to_cstring("file")
+
+        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcinitloadconfigandfonts.html
+        self.FcInitLoadConfigAndFonts = font_config.FcInitLoadConfigAndFonts
+        self.FcInitLoadConfigAndFonts.restype = c_void_p
+        self.FcInitLoadConfigAndFonts.argtypes = []
+
+        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcpatterncreate.html
+        self.FcPatternCreate = font_config.FcPatternCreate
+        self.FcPatternCreate.restype = c_void_p
+        self.FcPatternCreate.argtypes = []
+
+        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcobjectsetbuild.html
+        self.FcObjectSetBuild = font_config.FcObjectSetBuild
+        self.FcObjectSetBuild.restype = c_void_p
+        self.FcObjectSetBuild.argtypes = [c_char_p, c_void_p]
+
+        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcfontlist.html
+        self.FcFontList = font_config.FcFontList
+        self.FcFontList.restype = POINTER(FcFontSet)
+        self.FcFontList.argtypes = [c_void_p, c_void_p, c_void_p]
+
+        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcpatternget-type.html
+        self.FcPatternGetString = font_config.FcPatternGetString
+        self.FcPatternGetString.restype = FC_RESULT
+        self.FcPatternGetString.argtypes = [c_void_p, c_char_p, c_int, POINTER(c_char_p)]
+
+        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcconfigdestroy.html
+        self.FcConfigDestroy = font_config.FcConfigDestroy
+        self.FcConfigDestroy.restype = None
+        self.FcConfigDestroy.argtypes = [c_void_p]
+
+        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcpatterndestroy.html
+        self.FcPatternDestroy = font_config.FcPatternDestroy
+        self.FcPatternDestroy.restype = None
+        self.FcPatternDestroy.argtypes = [c_void_p]
+
+        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcobjectsetdestroy.html
+        self.FcObjectSetDestroy = font_config.FcObjectSetDestroy
+        self.FcObjectSetDestroy.restype = None
+        self.FcObjectSetDestroy.argtypes = [c_void_p]
+
+        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcfontsetdestroy.html
+        self.FcFontSetDestroy = font_config.FcFontSetDestroy
+        self.FcFontSetDestroy.restype = None
+        self.FcFontSetDestroy.argtypes = [c_void_p]
+
+        # https://fontconfig.pages.freedesktop.org/fontconfig/fontconfig-devel/fcconfiggetfontdirs.html
+        self.FcConfigGetFontDirs = font_config.FcConfigGetFontDirs
+        self.FcConfigGetFontDirs.restype = c_void_p
+        self.FcConfigGetFontDirs.argtypes = [c_void_p]
+
+        # Introduced in 2.11.0
+        if hasattr(font_config, "FcStrListFirst"):
+            # https://fontconfig.pages.freedesktop.org/fontconfig/fontconfig-devel/fcstrlistfirst.html
+            self.FcStrListFirst = font_config.FcStrListFirst
+            self.FcStrListFirst.restype = None
+            self.FcStrListFirst.argtypes = [c_void_p]
+
+        # https://fontconfig.pages.freedesktop.org/fontconfig/fontconfig-devel/fcstrlistnext.html
+        self.FcStrListNext = font_config.FcStrListNext
+        self.FcStrListNext.restype = c_char_p
+        self.FcStrListNext.argtypes = [c_void_p]
+
+        # https://fontconfig.pages.freedesktop.org/fontconfig/fontconfig-devel/fcstrlistdone.html
+        self.FcStrListDone = font_config.FcStrListDone
+        self.FcStrListDone.restype = None
+        self.FcStrListDone.argtypes = [c_void_p]
+
+        # https://fontconfig.pages.freedesktop.org/fontconfig/fontconfig-devel/fcconfiggetcurrent.html
+        self.FcConfigGetCurrent = font_config.FcConfigGetCurrent
+        self.FcConfigGetCurrent.restype = c_void_p
+        self.FcConfigGetCurrent.argtypes = []
+
+        # Introduced in 2.11.1
+        if hasattr(font_config, "FcDirCacheRescan"):
+            # https://fontconfig.pages.freedesktop.org/fontconfig/fontconfig-devel/fcdircacherescan.html
+            self.FcDirCacheRescan = font_config.FcDirCacheRescan
+            self.FcDirCacheRescan.restype = c_void_p
+            self.FcDirCacheRescan.argtypes = [c_char_p, c_void_p]
+
+        # https://fontconfig.pages.freedesktop.org/fontconfig/fontconfig-devel/fcgetversion.html
+        self.FcGetVersion = font_config.FcGetVersion
+        self.FcGetVersion.restype = c_int
+        self.FcGetVersion.argtypes = []
+
+
+    @staticmethod
+    def string_to_cstring(string: str) -> c_char_p:
+        return c_char_p(bytes(ord(c) for c in string))
diff --git a/find_system_fonts_filename/linux/linux_fonts.py b/find_system_fonts_filename/linux/linux_fonts.py
new file mode 100644
index 0000000..4cbe8b3
--- /dev/null
+++ b/find_system_fonts_filename/linux/linux_fonts.py
@@ -0,0 +1,110 @@
+from .fontconfig import FontConfig, FC_FONT_FORMAT, FC_RESULT
+import os
+from pathlib import Path
+from shutil import copyfile
+from ctypes import byref, c_char_p
+from typing import Set
+from ..exceptions import FindSystemFontsFilenameException, OSNotSupported
+from ..system_fonts import SystemFonts
+
+__all__ = ["LinuxFonts"]
+
+
+class LinuxFonts(SystemFonts):
+    VALID_FONT_FORMATS = [
+        FC_FONT_FORMAT.FT_FONT_FORMAT_TRUETYPE,
+        FC_FONT_FORMAT.FT_FONT_FORMAT_CFF,
+    ]
+
+    def get_system_fonts_filename() -> Set[str]:
+        """
+        Inspired by: https://stackoverflow.com/questions/10542832/how-to-use-fontconfig-to-get-font-list-c-c/14634033#14634033
+
+        Return an list of all the font installed.
+        """
+
+        fonts_filename = set()
+        font_config = FontConfig()
+
+        config = font_config.FcInitLoadConfigAndFonts()
+        pat = font_config.FcPatternCreate()
+        os = font_config.FcObjectSetBuild(font_config.FC_FILE, font_config.FC_FONTFORMAT, 0)
+        fs = font_config.FcFontList(config, pat, os)
+
+        for i in range(fs.contents.nfont):
+            font = fs.contents.fonts[i]
+            file_path_ptr = c_char_p()
+            font_format_ptr = c_char_p()
+
+            if (
+                font_config.FcPatternGetString(font, font_config.FC_FONTFORMAT, 0, byref(font_format_ptr)) == FC_RESULT.FC_RESULT_MATCH
+                and font_config.FcPatternGetString(font, font_config.FC_FILE, 0, byref(file_path_ptr)) == FC_RESULT.FC_RESULT_MATCH
+            ):
+                font_format = FC_FONT_FORMAT(font_format_ptr.value)
+
+                if font_format in LinuxFonts.VALID_FONT_FORMATS:
+                    # Decode with utf-8 since FcChar8
+                    fonts_filename.add(file_path_ptr.value.decode())
+
+        font_config.FcConfigDestroy(config)
+        font_config.FcPatternDestroy(pat)
+        font_config.FcObjectSetDestroy(os)
+        font_config.FcFontSetDestroy(fs)
+
+        return fonts_filename
+
+
+    def install_font(font_filename: Path, windows_flags: bool) -> None:
+        font_config = FontConfig()
+        version = font_config.FcGetVersion()
+
+        # We need 2.11.1 for FcDirCacheRescan
+        if version < 21101:
+            raise OSNotSupported("To install a font, you need to have at least the version 2.11.1 of fontconfig.")
+
+        config = font_config.FcConfigGetCurrent()
+        font_dirs = font_config.FcConfigGetFontDirs(config)
+        font_config.FcStrListFirst(font_dirs)
+
+        # We suppose that FcStrListNext always return the same Dirs
+        dirs_encoded = font_config.FcStrListNext(font_dirs)
+        if not dirs_encoded:
+            raise FindSystemFontsFilenameException(f"Couldn't get the font directory.")
+        dirs_decoded = dirs_encoded.decode("utf-8")
+
+        font_config.FcStrListDone(font_dirs)
+
+        copyfile(font_filename, os.path.join(dirs_decoded, font_filename.name))
+        font_config.FcDirCacheRescan(dirs_encoded, config)
+        font_config.FcConfigDestroy(config)
+
+
+    def uninstall_font(font_filename: Path, windows_flags: bool) -> None:
+        font_config = FontConfig()
+        version = font_config.FcGetVersion()
+
+        # We need 2.11.1 for FcDirCacheRescan
+        if version < 21101:
+            raise OSNotSupported("To install a font, you need to have at least the version 2.11.1 of fontconfig.")
+
+        config = font_config.FcConfigGetCurrent()
+        font_dirs = font_config.FcConfigGetFontDirs(config)
+        font_config.FcStrListFirst(font_dirs)
+
+        # We suppose that FcStrListNext always return the same Dirs
+        dirs_encoded = font_config.FcStrListNext(font_dirs)
+        if not dirs_encoded:
+            raise FindSystemFontsFilenameException(f"Couldn't get the font directory.")
+        dirs_decoded = dirs_encoded.decode("utf-8")
+
+        font_config.FcStrListDone(font_dirs)
+
+        file_path = os.path.join(dirs_decoded, font_filename.name)
+
+        if os.path.isfile(file_path):
+            os.remove(file_path)
+        else:
+            raise FindSystemFontsFilenameException(f"Couldn't get delete the font {font_filename}.")
+
+        font_config.FcDirCacheRescan(dirs_encoded, config)
+        font_config.FcConfigDestroy(config)
diff --git a/find_system_fonts_filename/linux_fonts.py b/find_system_fonts_filename/linux_fonts.py
deleted file mode 100644
index f3d9bbd..0000000
--- a/find_system_fonts_filename/linux_fonts.py
+++ /dev/null
@@ -1,136 +0,0 @@
-from ctypes import byref, c_char_p, c_int, c_void_p, cdll, POINTER, Structure, util
-from enum import Enum, IntEnum
-from typing import Set
-from .exceptions import FontConfigNotFound
-from .system_fonts import SystemFonts
-
-
-class FC_FONT_FORMAT(Enum):
-    # https://freetype.org/freetype2/docs/reference/ft2-font_formats.html#ft_get_font_format
-    # https://gitlab.freedesktop.org/freetype/freetype/-/blob/0a3836c97d5e84d6721ac0fd2839e8ae1b7be8d9/include/freetype/internal/services/svfntfmt.h#L36
-    FT_FONT_FORMAT_TRUETYPE = b"TrueType"
-    FT_FONT_FORMAT_TYPE_1 = b"Type 1"
-    FT_FONT_FORMAT_BDF = b"BDF"
-    FT_FONT_FORMAT_PCF = b"PCF"
-    FT_FONT_FORMAT_TYPE_42 = b"Type 42"
-    FT_FONT_FORMAT_CID = b"CID Type 1"
-    FT_FONT_FORMAT_CFF = b"CFF"
-    FT_FONT_FORMAT_PFR = b"PFR"
-    FT_FONT_FORMAT_WINFNT = b"Windows FNT"
-
-
-class FC_RESULT(IntEnum):
-    # https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/222d058525506e587a45368f10e45e4b80ca541f/fontconfig/fontconfig.h#L241
-    FC_RESULT_MATCH = 0
-    FC_RESULT_NO_MATCH = 1
-    FC_RESULT_TYPE_MISMATCH = 2
-    FC_RESULT_NO_ID = 3
-    FC_RESULT_OUT_OF_MEMORY = 4
-
-
-class FcFontSet(Structure):
-    # https://gitlab.freedesktop.org/fontconfig/fontconfig/-/blob/222d058525506e587a45368f10e45e4b80ca541f/fontconfig/fontconfig.h#L278
-    _fields_ = [
-        ("nfont", c_int),
-        ("sfont", c_int),
-        ("fonts", POINTER(POINTER(c_void_p))),
-    ]
-
-
-def string_to_cstring(string: str) -> c_char_p:
-    return c_char_p(bytes(ord(c) for c in string))
-
-
-FC_FONTFORMAT = string_to_cstring("fontformat")
-FC_FILE = string_to_cstring("file")
-
-
-class LinuxFonts(SystemFonts):
-    _font_config = None
-    VALID_FONT_FORMATS = [
-        FC_FONT_FORMAT.FT_FONT_FORMAT_TRUETYPE,
-        FC_FONT_FORMAT.FT_FONT_FORMAT_CFF,
-    ]
-
-    def get_system_fonts_filename() -> Set[str]:
-        """
-        Inspired by: https://stackoverflow.com/questions/10542832/how-to-use-fontconfig-to-get-font-list-c-c/14634033#14634033
-
-        Return an list of all the font installed.
-        """
-        if LinuxFonts._font_config is None:
-            LinuxFonts._load_font_config_library()
-
-        fonts_filename = set()
-
-        config = LinuxFonts._font_config.FcInitLoadConfigAndFonts()
-        pat = LinuxFonts._font_config.FcPatternCreate()
-        os = LinuxFonts._font_config.FcObjectSetBuild(FC_FILE, FC_FONTFORMAT, 0)
-        fs = LinuxFonts._font_config.FcFontList(config, pat, os)
-
-        for i in range(fs.contents.nfont):
-            font = fs.contents.fonts[i]
-            file_path_ptr = c_char_p()
-            font_format_ptr = c_char_p()
-
-            if (
-                LinuxFonts._font_config.FcPatternGetString(font, FC_FONTFORMAT, 0, byref(font_format_ptr)) == FC_RESULT.FC_RESULT_MATCH
-                and LinuxFonts._font_config.FcPatternGetString(font, FC_FILE, 0, byref(file_path_ptr)) == FC_RESULT.FC_RESULT_MATCH
-            ):
-                font_format = FC_FONT_FORMAT(font_format_ptr.value)
-
-                if font_format in LinuxFonts.VALID_FONT_FORMATS:
-                    # Decode with utf-8 since FcChar8
-                    fonts_filename.add(file_path_ptr.value.decode())
-
-        LinuxFonts._font_config.FcConfigDestroy(config)
-        LinuxFonts._font_config.FcPatternDestroy(pat)
-        LinuxFonts._font_config.FcObjectSetDestroy(os)
-        LinuxFonts._font_config.FcFontSetDestroy(fs)
-
-        return fonts_filename
-
-    @staticmethod
-    def _load_font_config_library():
-        font_config_library_name = util.find_library("fontconfig")
-
-        if font_config_library_name is None:
-            raise FontConfigNotFound("You need to install FontConfig to get the fonts filename")
-
-        LinuxFonts._font_config = cdll.LoadLibrary(font_config_library_name)
-
-        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcinitloadconfigandfonts.html
-        LinuxFonts._font_config.FcInitLoadConfigAndFonts.restype = c_void_p
-        LinuxFonts._font_config.FcInitLoadConfigAndFonts.argtypes = []
-
-        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcpatterncreate.html
-        LinuxFonts._font_config.FcPatternCreate.restype = c_void_p
-        LinuxFonts._font_config.FcPatternCreate.argtypes = []
-
-        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcobjectsetbuild.html
-        LinuxFonts._font_config.FcObjectSetBuild.restype = c_void_p
-        LinuxFonts._font_config.FcObjectSetBuild.argtypes = [c_char_p, c_void_p]
-
-        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcfontlist.html
-        LinuxFonts._font_config.FcFontList.restype = POINTER(FcFontSet)
-        LinuxFonts._font_config.FcFontList.argtypes = [c_void_p, c_void_p, c_void_p]
-
-        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcpatternget-type.html
-        LinuxFonts._font_config.FcPatternGetString.restype = FC_RESULT
-        LinuxFonts._font_config.FcPatternGetString.argtypes = [c_void_p, c_char_p, c_int, POINTER(c_char_p)]
-
-        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcconfigdestroy.html
-        LinuxFonts._font_config.FcConfigDestroy.restype = None
-        LinuxFonts._font_config.FcConfigDestroy.argtypes = [c_void_p]
-
-        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcpatterndestroy.html
-        LinuxFonts._font_config.FcPatternDestroy.restype = None
-        LinuxFonts._font_config.FcPatternDestroy.argtypes = [c_void_p]
-
-        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcobjectsetdestroy.html
-        LinuxFonts._font_config.FcObjectSetDestroy.restype = None
-        LinuxFonts._font_config.FcObjectSetDestroy.argtypes = [c_void_p]
-
-        # https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcfontsetdestroy.html
-        LinuxFonts._font_config.FcFontSetDestroy.restype = None
-        LinuxFonts._font_config.FcFontSetDestroy.argtypes = [c_void_p]
\ No newline at end of file
diff --git a/find_system_fonts_filename/mac/__init__.py b/find_system_fonts_filename/mac/__init__.py
new file mode 100644
index 0000000..d6ceee2
--- /dev/null
+++ b/find_system_fonts_filename/mac/__init__.py
@@ -0,0 +1,1 @@
+from .mac_fonts import *
diff --git a/find_system_fonts_filename/mac/core_foundation.py b/find_system_fonts_filename/mac/core_foundation.py
new file mode 100644
index 0000000..92b5d99
--- /dev/null
+++ b/find_system_fonts_filename/mac/core_foundation.py
@@ -0,0 +1,112 @@
+from .types import CFIndex, CFNumberRef, CFStringEncoding
+from ctypes import c_bool, c_char_p, c_void_p, cdll, util
+from enum import IntEnum
+
+__all__ = [
+    "CoreFoundation",
+    "CFURLPathStyle",
+    "CFStringBuiltInEncodings",
+    "CFNumberType",
+]
+
+
+class CFURLPathStyle(IntEnum):
+    # https://developer.apple.com/documentation/corefoundation/cfurlpathstyle?language=objc
+    kCFURLPOSIXPathStyle = 0
+    kCFURLHFSPathStyle = 1
+    kCFURLWindowsPathStyle = 2
+
+
+class CFStringBuiltInEncodings(IntEnum):
+    # https://developer.apple.com/documentation/corefoundation/cfstringbuiltinencodings?language=objc
+    kCFStringEncodingMacRoman = 0
+    kCFStringEncodingWindowsLatin1 = 0x0500
+    kCFStringEncodingISOLatin1 = 0x0201
+    kCFStringEncodingNextStepLatin = 0x0B01
+    kCFStringEncodingASCII = 0x0600
+    kCFStringEncodingUnicode = 0x0100
+    kCFStringEncodingUTF8 = 0x08000100
+    kCFStringEncodingNonLossyASCII = 0x0BFF
+    kCFStringEncodingUTF16 = 0x0100
+    kCFStringEncodingUTF16BE = 0x10000100
+    kCFStringEncodingUTF16LE = 0x14000100
+    kCFStringEncodingUTF32 = 0x0c000100
+    kCFStringEncodingUTF32BE = 0x18000100
+    kCFStringEncodingUTF32LE = 0x1c000100
+
+
+class CFNumberType(IntEnum):
+    # https://developer.apple.com/documentation/corefoundation/cfnumbertype?language=objc
+    kCFNumberSInt8Type = 1
+    kCFNumberSInt16Type = 2
+    kCFNumberSInt32Type = 3
+    kCFNumberSInt64Type = 4
+    kCFNumberFloat32Type = 5
+    kCFNumberFloat64Type = 6
+    kCFNumberCharType = 7
+    kCFNumberShortType = 8
+    kCFNumberIntType = 9
+    kCFNumberLongType = 10
+    kCFNumberLongLongType = 11
+    kCFNumberFloatType = 12
+    kCFNumberDoubleType = 13
+    kCFNumberCFIndexType = 14
+    kCFNumberNSIntegerType = 15
+    kCFNumberCGFloatType = 16
+    kCFNumberMaxType = 16
+
+
+class CoreFoundation():
+    def __init__(self) -> None:
+        core_foundation_library_name = util.find_library("CoreFoundation")
+        # Hack for compatibility with macOS greater or equals to 11.0.
+        # From: https://github.com/pyglet/pyglet/blob/a44e83a265e7df8ece793de865bcf3690f66adbd/pyglet/libs/darwin/cocoapy/cocoalibs.py#L10-L14
+        if core_foundation_library_name is None:
+            core_foundation_library_name = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"
+        core_foundation = cdll.LoadLibrary(core_foundation_library_name)
+
+
+        # https://developer.apple.com/documentation/corefoundation/1521153-cfrelease
+        self.CFRelease = core_foundation.CFRelease
+        self.CFRelease.restype = c_void_p
+        self.CFRelease.argtypes = [c_void_p]
+
+        # https://developer.apple.com/documentation/corefoundation/1388772-cfarraygetcount?language=objc
+        self.CFArrayGetCount = core_foundation.CFArrayGetCount
+        self.CFArrayGetCount.restype = CFIndex
+        self.CFArrayGetCount.argtypes = [c_void_p]
+
+        # https://developer.apple.com/documentation/corefoundation/1388767-cfarraygetvalueatindex?language=objc
+        self.CFArrayGetValueAtIndex = core_foundation.CFArrayGetValueAtIndex
+        self.CFArrayGetValueAtIndex.restype = c_void_p
+        self.CFArrayGetValueAtIndex.argtypes = [c_void_p, CFIndex]
+
+        # https://developer.apple.com/documentation/corefoundation/1542853-cfstringgetlength?language=objc
+        self.CFStringGetLength = core_foundation.CFStringGetLength
+        self.CFStringGetLength.restype = CFIndex
+        self.CFStringGetLength.argtypes = [c_void_p]
+
+        # https://developer.apple.com/documentation/corefoundation/1542143-cfstringgetmaximumsizeforencodin?language=objc
+        self.CFStringGetMaximumSizeForEncoding = core_foundation.CFStringGetMaximumSizeForEncoding
+        self.CFStringGetMaximumSizeForEncoding.restype = CFIndex
+        self.CFStringGetMaximumSizeForEncoding.argtypes = [c_void_p, CFStringEncoding]
+
+        # https://developer.apple.com/documentation/corefoundation/1542721-cfstringgetcstring?language=objc
+        self.CFStringGetCString = core_foundation.CFStringGetCString
+        self.CFStringGetCString.restype = c_bool
+        self.CFStringGetCString.argtypes = [c_void_p, c_char_p, CFIndex, CFStringEncoding]
+
+        # https://developer.apple.com/documentation/corefoundation/1543114-cfnumbergetvalue?language=objc
+        self.CFNumberGetValue = core_foundation.CFNumberGetValue
+        self.CFNumberGetValue.restype = c_bool
+        self.CFNumberGetValue.argtypes = [CFNumberRef, CFIndex, c_void_p]
+
+        # https://developer.apple.com/documentation/corefoundation/1542942-cfstringcreatewithcstring?language=objc
+        self.CFStringCreateWithCString = core_foundation.CFStringCreateWithCString
+        self.CFStringCreateWithCString.restype = c_void_p
+        self.CFStringCreateWithCString.argtypes = [c_void_p, c_char_p, CFStringEncoding]
+
+        # https://developer.apple.com/documentation/corefoundation/1543250-cfurlcreatewithfilesystempath?language=objc
+        self.CFURLCreateWithFileSystemPath = core_foundation.CFURLCreateWithFileSystemPath
+        self.CFURLCreateWithFileSystemPath.restype = c_void_p
+        self.CFURLCreateWithFileSystemPath.argtypes = [c_void_p, c_void_p, CFIndex, c_bool]
diff --git a/find_system_fonts_filename/mac/core_text.py b/find_system_fonts_filename/mac/core_text.py
new file mode 100644
index 0000000..a05c90e
--- /dev/null
+++ b/find_system_fonts_filename/mac/core_text.py
@@ -0,0 +1,71 @@
+from .types import CFIndex
+from ctypes import c_bool, c_uint32, c_void_p, cdll, util
+from enum import IntEnum
+
+__all__ = [
+    "CoreText",
+    "CTFontFormat",
+    "CTFontManagerScope"
+]
+
+
+class CTFontFormat(IntEnum):
+    # https://developer.apple.com/documentation/coretext/ctfontformat?language=objc
+    kCTFontFormatUnrecognized = 0
+    kCTFontFormatOpenTypePostScript = 1
+    kCTFontFormatOpenTypeTrueType = 2
+    kCTFontFormatTrueType = 3
+    kCTFontFormatPostScript = 4
+    kCTFontFormatBitmap = 5
+
+
+class CTFontManagerScope(IntEnum):
+    # https://developer.apple.com/documentation/coretext/ctfontmanagerscope?language=objc
+    kCTFontManagerScopeNone = 0
+    kCTFontManagerScopeProcess = 1
+    kCTFontManagerScopePersistent = 2
+    kCTFontManagerScopeSession = 3
+    kCTFontManagerScopeUser = 2
+
+
+class CoreText():
+    def __init__(self) -> None:
+        core_text_library_name = util.find_library("CoreText")
+        # Hack for compatibility with macOS greater or equals to 11.0.
+        # From: https://github.com/pyglet/pyglet/blob/a44e83a265e7df8ece793de865bcf3690f66adbd/pyglet/libs/darwin/cocoapy/cocoalibs.py#L520-L524
+        if core_text_library_name is None:
+            core_text_library_name = "/System/Library/Frameworks/CoreText.framework/CoreText"
+        core_text = cdll.LoadLibrary(core_text_library_name)
+
+        self.kCTFontURLAttribute = c_void_p.in_dll(core_text, "kCTFontURLAttribute")
+        self.kCTFontFormatAttribute = c_void_p.in_dll(core_text, "kCTFontFormatAttribute")
+
+        # https://developer.apple.com/documentation/coretext/1509907-ctfontcollectioncreatefromavaila?language=objc
+        self.CTFontCollectionCreateFromAvailableFonts = core_text.CTFontCollectionCreateFromAvailableFonts
+        self.CTFontCollectionCreateFromAvailableFonts.restype = c_void_p
+        self.CTFontCollectionCreateFromAvailableFonts.argtypes = [c_void_p]
+
+        # https://developer.apple.com/documentation/coretext/1511091-ctfontcollectioncreatematchingfo?language=objc
+        self.CTFontCollectionCreateMatchingFontDescriptors = core_text.CTFontCollectionCreateMatchingFontDescriptors
+        self.CTFontCollectionCreateMatchingFontDescriptors.restype = c_void_p
+        self.CTFontCollectionCreateMatchingFontDescriptors.argtypes = [c_void_p]
+
+        # https://developer.apple.com/documentation/coretext/1510346-ctfontdescriptorcopyattribute?language=objc
+        self.CTFontDescriptorCopyAttribute = core_text.CTFontDescriptorCopyAttribute
+        self.CTFontDescriptorCopyAttribute.restype = c_void_p
+        self.CTFontDescriptorCopyAttribute.argtypes = [c_void_p, c_void_p]
+
+        # https://developer.apple.com/documentation/corefoundation/1541581-cfurlcopyfilesystempath?language=objc
+        self.CFURLCopyFileSystemPath = core_text.CFURLCopyFileSystemPath
+        self.CFURLCopyFileSystemPath.restype = c_void_p
+        self.CFURLCopyFileSystemPath.argtypes = [c_void_p, CFIndex]
+
+        # https://developer.apple.com/documentation/coretext/1499468-ctfontmanagerregisterfontsforurl?language=objc
+        self.CTFontManagerRegisterFontsForURL = core_text.CTFontManagerRegisterFontsForURL
+        self.CTFontManagerRegisterFontsForURL.restype = c_bool
+        self.CTFontManagerRegisterFontsForURL.argtypes = [c_void_p,  c_uint32, c_void_p]
+
+        # https://developer.apple.com/documentation/coretext/1499496-ctfontmanagerunregisterfontsforu?language=objc
+        self.CTFontManagerUnregisterFontsForURL = core_text.CTFontManagerUnregisterFontsForURL
+        self.CTFontManagerUnregisterFontsForURL.restype = c_bool
+        self.CTFontManagerUnregisterFontsForURL.argtypes = [c_void_p,  c_uint32, c_void_p]
diff --git a/find_system_fonts_filename/mac/mac_fonts.py b/find_system_fonts_filename/mac/mac_fonts.py
new file mode 100644
index 0000000..697b565
--- /dev/null
+++ b/find_system_fonts_filename/mac/mac_fonts.py
@@ -0,0 +1,137 @@
+from .core_foundation import CFNumberType, CFStringBuiltInEncodings, CFURLPathStyle, CoreFoundation
+from .core_text import CoreText, CTFontFormat, CTFontManagerScope
+from .version_helpers import MacVersionHelpers
+from ctypes import byref, c_uint32, c_void_p, create_string_buffer
+from pathlib import Path
+from typing import Set
+from ..exceptions import FindSystemFontsFilenameException, OSNotSupported, SystemApiError
+from ..system_fonts import SystemFonts
+
+__all__ = ["MacFonts"]
+
+
+class MacFonts(SystemFonts):
+    # CoreText has an API to get the format of the font: https://developer.apple.com/documentation/coretext/ctfontformat
+    # But, the API is "semi-broken" since it says .dfont are TrueType. This is kinda true, but it is not a behaviour that we want.
+    # So, we also need to check the file extension to see if the file is valid.
+    VALID_FONT_FORMATS = ["ttf", "otf", "ttc", "otc"]
+
+    def get_system_fonts_filename() -> Set[str]:
+        if not MacVersionHelpers.is_mac_version_or_greater(10, 6):
+            raise OSNotSupported("FindSystemFontsFilename only works on Mac 10.6 or more")
+
+        fonts_filename = set()
+        core_foundation = CoreFoundation()
+        core_text = CoreText()
+
+        font_collection = core_text.CTFontCollectionCreateFromAvailableFonts(None)
+        font_array = core_text.CTFontCollectionCreateMatchingFontDescriptors(font_collection)
+        font_count = core_foundation.CFArrayGetCount(font_array)
+
+        for i in range(font_count):
+            font_descriptor = core_foundation.CFArrayGetValueAtIndex(font_array, i)
+
+            font_format_ptr = core_text.CTFontDescriptorCopyAttribute(font_descriptor, core_text.kCTFontFormatAttribute)
+            font_format = CTFontFormat(MacFonts._cfnumber_to_uint32(font_format_ptr))
+            core_foundation.CFRelease(font_format_ptr)
+
+            if font_format not in (
+                CTFontFormat.kCTFontFormatOpenTypePostScript,
+                CTFontFormat.kCTFontFormatOpenTypeTrueType,
+                CTFontFormat.kCTFontFormatTrueType,
+                CTFontFormat.kCTFontFormatPostScript
+            ):
+                continue
+
+            url_ptr = core_text.CTFontDescriptorCopyAttribute(font_descriptor, core_text.kCTFontURLAttribute)
+            path_ptr = core_text.CFURLCopyFileSystemPath(url_ptr, CFURLPathStyle.kCFURLPOSIXPathStyle)
+            filename = MacFonts._cfstring_to_string(path_ptr)
+            core_foundation.CFRelease(path_ptr)
+            core_foundation.CFRelease(url_ptr)
+
+            if Path(filename).suffix.lstrip(".").strip().lower() not in MacFonts.VALID_FONT_FORMATS:
+                continue
+
+            fonts_filename.add(filename)
+
+        core_foundation.CFRelease(font_array)
+        core_foundation.CFRelease(font_collection)
+
+        return fonts_filename
+
+
+    def install_font(font_filename: Path, windows_flags: bool) -> None:
+        if not MacVersionHelpers.is_mac_version_or_greater(10, 6):
+            raise OSNotSupported("FindSystemFontsFilename only works on Mac 10.6 or more")
+
+        core_foundation = CoreFoundation()
+        core_text = CoreText()
+
+        file_path_string_ref = core_foundation.CFStringCreateWithCString(None, str(font_filename).encode("utf-8"), CFStringBuiltInEncodings.kCFStringEncodingUTF8)
+        url = core_foundation.CFURLCreateWithFileSystemPath(None, file_path_string_ref, CFURLPathStyle.kCFURLPOSIXPathStyle, False)
+
+        is_success = core_text.CTFontManagerRegisterFontsForURL(url, CTFontManagerScope.kCTFontManagerScopeUser, None)
+
+        core_foundation.CFRelease(url)
+        core_foundation.CFRelease(file_path_string_ref)
+
+        if not is_success:
+            raise FindSystemFontsFilenameException(f"The font file \"{font_filename}\" could not be installed.")
+
+
+    def uninstall_font(font_filename: Path, windows_flags: bool) -> None:
+        if not MacVersionHelpers.is_mac_version_or_greater(10, 6):
+            raise OSNotSupported("FindSystemFontsFilename only works on Mac 10.6 or more")
+
+        core_foundation = CoreFoundation()
+        core_text = CoreText()
+
+        file_path_string_ref = core_foundation.CFStringCreateWithCString(None, str(font_filename).encode("utf-8"), CFStringBuiltInEncodings.kCFStringEncodingUTF8)
+        url = core_foundation.CFURLCreateWithFileSystemPath(None, file_path_string_ref, CFURLPathStyle.kCFURLPOSIXPathStyle, False)
+
+        is_success = core_text.CTFontManagerUnregisterFontsForURL(url, CTFontManagerScope.kCTFontManagerScopeUser, None)
+
+        core_foundation.CFRelease(url)
+        core_foundation.CFRelease(file_path_string_ref)
+
+        if not is_success:
+            raise FindSystemFontsFilenameException(f"The font file \"{font_filename}\" could not be uninstalled.")
+
+
+    @staticmethod
+    def _cfstring_to_string(cfstring: c_void_p) -> str:
+        """
+        Parameters:
+            cfstring (c_void_p): An CFString instance.
+        Returns:
+            The decoded CFString.
+        """
+        core_foundation = CoreFoundation()
+
+        length = core_foundation.CFStringGetLength(cfstring)
+        size = core_foundation.CFStringGetMaximumSizeForEncoding(length, CFStringBuiltInEncodings.kCFStringEncodingUTF8)
+        buffer = create_string_buffer(size + 1)
+        result = core_foundation.CFStringGetCString(cfstring, buffer, len(buffer), CFStringBuiltInEncodings.kCFStringEncodingUTF8)
+        if result:
+            return str(buffer.value, "utf-8")
+        else:
+            raise SystemApiError("An unexpected error has occurred while decoding the CFString.")
+
+
+    @staticmethod
+    def _cfnumber_to_uint32(cfnumber: c_void_p) -> int:
+        """
+        Parameters:
+            cfnumber (c_void_p): An CFNumber instance.
+        Returns:
+            The decoded CFNumber.
+            If the CFNumber is not an int, it will raise an exception
+        """
+        core_foundation = CoreFoundation()
+
+        value = c_uint32()
+        success = core_foundation.CFNumberGetValue(cfnumber, CFNumberType.kCFNumberIntType, byref(value))
+        if success:
+            return value.value
+        else:
+            raise SystemApiError("The CFNumber doesn't seems to be an kCFNumberIntType.")
diff --git a/find_system_fonts_filename/mac/types.py b/find_system_fonts_filename/mac/types.py
new file mode 100644
index 0000000..b38d029
--- /dev/null
+++ b/find_system_fonts_filename/mac/types.py
@@ -0,0 +1,5 @@
+from ctypes import c_long, c_uint32, c_void_p
+
+CFIndex = c_long
+CFStringEncoding = c_uint32
+CFNumberRef = c_void_p
diff --git a/find_system_fonts_filename/mac/version_helpers.py b/find_system_fonts_filename/mac/version_helpers.py
new file mode 100644
index 0000000..e0ac7b1
--- /dev/null
+++ b/find_system_fonts_filename/mac/version_helpers.py
@@ -0,0 +1,24 @@
+from platform import mac_ver
+
+__all__ = [
+    "MacVersionHelpers"
+]
+
+
+class MacVersionHelpers:
+    @staticmethod
+    def is_mac_version_or_greater(minimum_major: int, minimum_minor: int) -> bool:
+        """
+        Parameters:
+            major (int): The minimum major OS version number.
+            minor (int): The minimum minor OS version number.
+        Returns:
+            True if the specified version matches or if it is greater than the version of the current Mac OS. Otherwise, False.
+        """
+
+        system_major, system_minor = mac_ver()[0].split(".")[:2]
+
+        system_major = int(system_major)
+        system_minor = int(system_minor)
+
+        return system_major > minimum_major or (system_major == minimum_major and system_minor >= minimum_minor)
diff --git a/find_system_fonts_filename/mac_fonts.py b/find_system_fonts_filename/mac_fonts.py
deleted file mode 100644
index 79a8c17..0000000
--- a/find_system_fonts_filename/mac_fonts.py
+++ /dev/null
@@ -1,241 +0,0 @@
-from ctypes import byref, c_bool, c_char_p, c_long, c_uint32, c_void_p, cdll, create_string_buffer, util
-from enum import IntEnum
-from pathlib import Path
-from platform import mac_ver
-from typing import Set
-from .exceptions import OSNotSupported
-from .system_fonts import SystemFonts
-
-
-class CTFontFormat(IntEnum):
-    # https://developer.apple.com/documentation/coretext/ctfontformat?language=objc
-    kCTFontFormatUnrecognized = 0
-    kCTFontFormatOpenTypePostScript = 1
-    kCTFontFormatOpenTypeTrueType = 2
-    kCTFontFormatTrueType = 3
-    kCTFontFormatPostScript = 4
-    kCTFontFormatBitmap = 5
-
-
-class CFURLPathStyle(IntEnum):
-    # https://developer.apple.com/documentation/corefoundation/cfurlpathstyle?language=objc
-    kCFURLPOSIXPathStyle = 0
-    kCFURLHFSPathStyle = 1
-    kCFURLWindowsPathStyle = 2
-
-
-class CFStringBuiltInEncodings(IntEnum):
-    # https://developer.apple.com/documentation/corefoundation/cfstringbuiltinencodings?language=objc
-    kCFStringEncodingMacRoman = 0
-    kCFStringEncodingWindowsLatin1 = 0x0500
-    kCFStringEncodingISOLatin1 = 0x0201
-    kCFStringEncodingNextStepLatin = 0x0B01
-    kCFStringEncodingASCII = 0x0600
-    kCFStringEncodingUnicode = 0x0100
-    kCFStringEncodingUTF8 = 0x08000100
-    kCFStringEncodingNonLossyASCII = 0x0BFF
-    kCFStringEncodingUTF16 = 0x0100
-    kCFStringEncodingUTF16BE = 0x10000100
-    kCFStringEncodingUTF16LE = 0x14000100
-    kCFStringEncodingUTF32 = 0x0c000100
-    kCFStringEncodingUTF32BE = 0x18000100
-    kCFStringEncodingUTF32LE = 0x1c000100
-
-
-class CFNumberType(IntEnum):
-    # https://developer.apple.com/documentation/corefoundation/cfnumbertype?language=objc
-    kCFNumberSInt8Type = 1
-    kCFNumberSInt16Type = 2
-    kCFNumberSInt32Type = 3
-    kCFNumberSInt64Type = 4
-    kCFNumberFloat32Type = 5
-    kCFNumberFloat64Type = 6
-    kCFNumberCharType = 7
-    kCFNumberShortType = 8
-    kCFNumberIntType = 9
-    kCFNumberLongType = 10
-    kCFNumberLongLongType = 11
-    kCFNumberFloatType = 12
-    kCFNumberDoubleType = 13
-    kCFNumberCFIndexType = 14
-    kCFNumberNSIntegerType = 15
-    kCFNumberCGFloatType = 16
-    kCFNumberMaxType = 16
-
-
-class MacFonts(SystemFonts):
-    _core_foundation = None
-    _core_text = None
-    _kCTFontURLAttribute = None
-    _kCTFontFormatAttribute = None
-    # CoreText has an API to get the format of the font: https://developer.apple.com/documentation/coretext/ctfontformat
-    # But, the API is "semi-broken" since it says .dfont are TrueType. This is kinda true, but it is not a behaviour that we want.
-    # So, we also need to check the file extension to see if the file is valid.
-    VALID_FONT_FORMATS = ["ttf", "otf", "ttc", "otc"]
-
-    def get_system_fonts_filename() -> Set[str]:
-        if MacVersionHelpers.is_mac_version_or_greater(10, 6):
-            if MacFonts._core_foundation is None or MacFonts._core_text is None:
-                MacFonts._load_core_library()
-
-            fonts_filename = set()
-
-            font_collection = MacFonts._core_text.CTFontCollectionCreateFromAvailableFonts(None)
-            font_array = MacFonts._core_text.CTFontCollectionCreateMatchingFontDescriptors(font_collection)
-            font_count = MacFonts._core_foundation.CFArrayGetCount(font_array)
-
-            for i in range(font_count):
-                font_descriptor = MacFonts._core_foundation.CFArrayGetValueAtIndex(font_array, i)
-
-                font_format_ptr = MacFonts._core_text.CTFontDescriptorCopyAttribute(font_descriptor, MacFonts._kCTFontFormatAttribute)
-                font_format = CTFontFormat(MacFonts._cfnumber_to_uint32(font_format_ptr))
-                MacFonts._core_foundation.CFRelease(font_format_ptr)
-
-                if font_format not in (
-                    CTFontFormat.kCTFontFormatOpenTypePostScript,
-                    CTFontFormat.kCTFontFormatOpenTypeTrueType,
-                    CTFontFormat.kCTFontFormatTrueType,
-                    CTFontFormat.kCTFontFormatPostScript
-                ):
-                    continue
-
-                url_ptr = MacFonts._core_text.CTFontDescriptorCopyAttribute(font_descriptor, MacFonts._kCTFontURLAttribute)
-                path_ptr = MacFonts._core_text.CFURLCopyFileSystemPath(url_ptr, CFURLPathStyle.kCFURLPOSIXPathStyle)
-                filename = MacFonts._cfstring_to_string(path_ptr)
-                MacFonts._core_foundation.CFRelease(path_ptr)
-                MacFonts._core_foundation.CFRelease(url_ptr)
-
-                if Path(filename).suffix.lstrip(".").strip().lower() not in MacFonts.VALID_FONT_FORMATS:
-                    continue
-
-                fonts_filename.add(filename)
-
-            MacFonts._core_foundation.CFRelease(font_array)
-            MacFonts._core_foundation.CFRelease(font_collection)
-        else:
-            raise OSNotSupported("FindSystemFontsFilename only works on Mac 10.6 or more")
-
-        return fonts_filename
-
-
-    @staticmethod
-    def _cfstring_to_string(cfstring: c_void_p) -> str:
-        """
-        Parameters:
-            cfstring (c_void_p): An CFString instance.
-        Returns:
-            The decoded CFString.
-        """
-        length = MacFonts._core_foundation.CFStringGetLength(cfstring)
-        size = MacFonts._core_foundation.CFStringGetMaximumSizeForEncoding(length, CFStringBuiltInEncodings.kCFStringEncodingUTF8)
-        buffer = create_string_buffer(size + 1)
-        result = MacFonts._core_foundation.CFStringGetCString(cfstring, buffer, len(buffer), CFStringBuiltInEncodings.kCFStringEncodingUTF8)
-        if result:
-            return str(buffer.value, "utf-8")
-        else:
-            raise Exception("An unexpected error has occurred while decoding the CFString.")
-
-
-    @staticmethod
-    def _cfnumber_to_uint32(cfnumber: c_void_p) -> int:
-        """
-        Parameters:
-            cfnumber (c_void_p): An CFNumber instance.
-        Returns:
-            The decoded CFNumber.
-            If the CFNumber is not an int, it will raise an exception
-        """
-        value = c_uint32()
-        success = MacFonts._core_foundation.CFNumberGetValue(cfnumber, CFNumberType.kCFNumberIntType, byref(value))
-        if success:
-            return value.value
-        else:
-            raise Exception("The CFNumber doesn't seems to be an kCFNumberIntType.")
-
-
-    @staticmethod
-    def _load_core_library():
-        core_foundation_library_name = util.find_library("CoreFoundation")
-        # Hack for compatibility with macOS greater or equals to 11.0.
-        # From: https://github.com/pyglet/pyglet/blob/a44e83a265e7df8ece793de865bcf3690f66adbd/pyglet/libs/darwin/cocoapy/cocoalibs.py#L10-L14
-        if core_foundation_library_name is None:
-            core_foundation_library_name = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"
-        MacFonts._core_foundation = cdll.LoadLibrary(core_foundation_library_name)
-
-        core_text_library_name = util.find_library("CoreText")
-        # Hack for compatibility with macOS greater or equals to 11.0.
-        # From: https://github.com/pyglet/pyglet/blob/a44e83a265e7df8ece793de865bcf3690f66adbd/pyglet/libs/darwin/cocoapy/cocoalibs.py#L520-L524
-        if core_text_library_name is None:
-            core_text_library_name = "/System/Library/Frameworks/CoreText.framework/CoreText"
-        MacFonts._core_text = cdll.LoadLibrary(core_text_library_name)
-
-        CFIndex = c_long
-        CFStringEncoding = c_uint32
-        CFNumberRef = c_void_p
-        CFNumberType = c_long
-
-        # https://developer.apple.com/documentation/corefoundation/1521153-cfrelease
-        MacFonts._core_foundation.CFRelease.restype = c_void_p
-        MacFonts._core_foundation.CFRelease.argtypes = [c_void_p]
-
-        # https://developer.apple.com/documentation/corefoundation/1388772-cfarraygetcount?language=objc
-        MacFonts._core_foundation.CFArrayGetCount.restype = CFIndex
-        MacFonts._core_foundation.CFArrayGetCount.argtypes = [c_void_p]
-
-        # https://developer.apple.com/documentation/corefoundation/1388767-cfarraygetvalueatindex?language=objc
-        MacFonts._core_foundation.CFArrayGetValueAtIndex.restype = c_void_p
-        MacFonts._core_foundation.CFArrayGetValueAtIndex.argtypes = [c_void_p, CFIndex]
-
-        # https://developer.apple.com/documentation/corefoundation/1542853-cfstringgetlength?language=objc
-        MacFonts._core_foundation.CFStringGetLength.restype = CFIndex
-        MacFonts._core_foundation.CFStringGetLength.argtypes = [c_void_p]
-
-        # https://developer.apple.com/documentation/corefoundation/1542143-cfstringgetmaximumsizeforencodin?language=objc
-        MacFonts._core_foundation.CFStringGetMaximumSizeForEncoding.restype = CFIndex
-        MacFonts._core_foundation.CFStringGetMaximumSizeForEncoding.argtypes = [c_void_p, CFStringEncoding]
-
-        # https://developer.apple.com/documentation/corefoundation/1542721-cfstringgetcstring?language=objc
-        MacFonts._core_foundation.CFStringGetCString.restype = c_bool
-        MacFonts._core_foundation.CFStringGetCString.argtypes = [c_void_p, c_char_p, CFIndex, CFStringEncoding]
-
-        # https://developer.apple.com/documentation/corefoundation/1543114-cfnumbergetvalue?language=objc
-        MacFonts._core_foundation.CFNumberGetValue.restype = c_bool
-        MacFonts._core_foundation.CFNumberGetValue.argtypes = [CFNumberRef, CFNumberType, c_void_p]
-
-        # https://developer.apple.com/documentation/coretext/1509907-ctfontcollectioncreatefromavaila?language=objc
-        MacFonts._core_text.CTFontCollectionCreateFromAvailableFonts.restype = c_void_p
-        MacFonts._core_text.CTFontCollectionCreateFromAvailableFonts.argtypes = [c_void_p]
-
-        # https://developer.apple.com/documentation/coretext/1511091-ctfontcollectioncreatematchingfo?language=objc
-        MacFonts._core_text.CTFontCollectionCreateMatchingFontDescriptors.restype = c_void_p
-        MacFonts._core_text.CTFontCollectionCreateMatchingFontDescriptors.argtypes = [c_void_p]
-
-        # https://developer.apple.com/documentation/coretext/1510346-ctfontdescriptorcopyattribute?language=objc
-        MacFonts._core_text.CTFontDescriptorCopyAttribute.restype = c_void_p
-        MacFonts._core_text.CTFontDescriptorCopyAttribute.argtypes = [c_void_p, c_void_p]
-
-        # https://developer.apple.com/documentation/corefoundation/1541581-cfurlcopyfilesystempath?language=objc
-        MacFonts._core_text.CFURLCopyFileSystemPath.restype = c_void_p
-        MacFonts._core_text.CFURLCopyFileSystemPath.argtypes = [c_void_p, CFIndex]
-
-        MacFonts._kCTFontURLAttribute = c_void_p.in_dll(MacFonts._core_text, "kCTFontURLAttribute")
-        MacFonts._kCTFontFormatAttribute = c_void_p.in_dll(MacFonts._core_text, "kCTFontFormatAttribute")
-
-
-class MacVersionHelpers:
-    @staticmethod
-    def is_mac_version_or_greater(minimum_major: int, minimum_minor: int) -> bool:
-        """
-        Parameters:
-            major (int): The minimum major OS version number.
-            minor (int): The minimum minor OS version number.
-        Returns:
-            True if the specified version matches or if it is greater than the version of the current Mac OS. Otherwise, False.
-        """
-
-        system_major, system_minor = mac_ver()[0].split(".")[:2]
-
-        system_major = int(system_major)
-        system_minor = int(system_minor)
-
-        return system_major > minimum_major or (system_major == minimum_major and system_minor >= minimum_minor)
diff --git a/find_system_fonts_filename/system_fonts.py b/find_system_fonts_filename/system_fonts.py
index 1870da7..400f508 100644
--- a/find_system_fonts_filename/system_fonts.py
+++ b/find_system_fonts_filename/system_fonts.py
@@ -1,4 +1,5 @@
 from abc import ABC, abstractmethod
+from pathlib import Path
 from typing import Set
 
 
@@ -10,3 +11,19 @@ def get_system_fonts_filename() -> Set[str]:
         Return an set of all the installed fonts filename.
         """
         pass
+
+    @staticmethod
+    @abstractmethod
+    def install_font(font_filename: Path, add_font_to_registry: bool = False) -> None:
+        """
+        Install a font from it's filename.
+        """
+        pass
+
+    @staticmethod
+    @abstractmethod
+    def uninstall_font(font_filename: Path, remove_font_in_registry: bool) -> None:
+        """
+        Uninstall a font from it's filename.
+        """
+        pass
diff --git a/find_system_fonts_filename/windows/__init__.py b/find_system_fonts_filename/windows/__init__.py
new file mode 100644
index 0000000..bf31842
--- /dev/null
+++ b/find_system_fonts_filename/windows/__init__.py
@@ -0,0 +1,1 @@
+from .windows_fonts import *
diff --git a/find_system_fonts_filename/windows/advapi32.py b/find_system_fonts_filename/windows/advapi32.py
new file mode 100644
index 0000000..4ae3775
--- /dev/null
+++ b/find_system_fonts_filename/windows/advapi32.py
@@ -0,0 +1,71 @@
+from ctypes import windll, wintypes
+from enum import Enum
+from ..exceptions import SystemApiError
+
+__all__ = [
+    "Advapi32",
+    "RegistryDataType"
+]
+
+class RegistryDataType(Enum):
+    REG_NONE = 0
+    REG_SZ = 1
+    REG_EXPAND_SZ = 2
+    REG_BINARY = 3
+    REG_DWORD = 4
+    REG_DWORD_LITTLE_ENDIAN = 4
+    REG_DWORD_BIG_ENDIAN = 5
+    REG_LINK = 6
+    REG_MULTI_SZ = 7
+    REG_RESOURCE_LIST = 8
+    REG_FULL_RESOURCE_DESCRIPTOR = 9
+    REG_RESOURCE_REQUIREMENTS_LIST = 10
+    REG_QWORD = 11
+    REG_QWORD_LITTLE_ENDIAN = REG_QWORD
+
+
+class Advapi32():
+    def __init__(self) -> None:
+        advapi32 = windll.advapi32
+
+        self.HKEY_CURRENT_USER = wintypes.HKEY(0x80000001)
+        self.KEY_SET_VALUE = 0x0002
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regopenkeyexw
+        self.RegOpenKeyExW = advapi32.RegOpenKeyExW
+        self.RegOpenKeyExW.restype = wintypes.LONG
+        self.RegOpenKeyExW.argtypes = [wintypes.HKEY, wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD, wintypes.PHKEY]
+        self.RegOpenKeyExW.errcheck = self.errcheck_lstatus_fails
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regsetvalueexw
+        self.RegSetValueExW = advapi32.RegSetValueExW
+        self.RegSetValueExW.restype = wintypes.LONG
+        self.RegSetValueExW.argtypes = [wintypes.HKEY, wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD, wintypes.LPBYTE, wintypes.DWORD]
+        self.RegSetValueExW.errcheck = self.errcheck_lstatus_fails
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regdeletevaluew
+        self.RegDeleteValueW = advapi32.RegDeleteValueW
+        self.RegDeleteValueW.restype = wintypes.LONG
+        self.RegDeleteValueW.argtypes = [wintypes.HKEY, wintypes.LPCWSTR]
+        self.RegDeleteValueW.errcheck = self.errcheck_regdeletevaluew_fails
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regclosekey
+        self.RegCloseKey = advapi32.RegCloseKey
+        self.RegCloseKey.restype = wintypes.LONG
+        self.RegCloseKey.argtypes = [wintypes.HKEY]
+        self.RegCloseKey.errcheck = self.errcheck_lstatus_fails
+
+    @staticmethod
+    def errcheck_lstatus_fails(result, func, args):
+        ERROR_SUCCESS = 0
+        if result != ERROR_SUCCESS:
+            raise SystemApiError(f"{func.__name__} fails. The result is {result} which is invalid")
+        return result
+
+    @staticmethod
+    def errcheck_regdeletevaluew_fails(result, func, args):
+        ERROR_SUCCESS = 0
+        ERROR_FILE_NOT_FOUND = 2
+        if result not in (ERROR_SUCCESS, ERROR_FILE_NOT_FOUND):
+            raise SystemApiError(f"{func.__name__} fails. The result is {result} which is invalid")
+        return result
diff --git a/find_system_fonts_filename/windows_fonts.py b/find_system_fonts_filename/windows/dwrite.py
similarity index 50%
rename from find_system_fonts_filename/windows_fonts.py
rename to find_system_fonts_filename/windows/dwrite.py
index f0ca6fe..1db9433 100644
--- a/find_system_fonts_filename/windows_fonts.py
+++ b/find_system_fonts_filename/windows/dwrite.py
@@ -1,11 +1,36 @@
-from comtypes import COMError, GUID, HRESULT, IUnknown, STDMETHOD
-from ctypes import byref, create_unicode_buffer, POINTER, windll, wintypes
-from enum import IntEnum
-from os.path import isfile
-from sys import getwindowsversion
-from typing import Set
-from .exceptions import OSNotSupported
-from .system_fonts import SystemFonts
+from .gdi32 import LOGFONTW
+from comtypes import GUID, HRESULT, IUnknown, STDMETHOD
+from ctypes import POINTER, windll, wintypes
+from enum import IntEnum, IntFlag
+
+__all__ = [
+    "DWrite",
+    "DWRITE_FACTORY_TYPE",
+    "DWRITE_FONT_FILE_TYPE",
+    "DWRITE_FONT_SIMULATIONS",
+    "DWRITE_INFORMATIONAL_STRING_ID",
+    "DWRITE_LOCALITY",
+    "IDWriteFactory",
+    "IDWriteFactory1",
+    "IDWriteFactory2",
+    "IDWriteFactory3",
+    "IDWriteFont",
+    "IDWriteFontCollection",
+    "IDWriteFontCollection1",
+    "IDWriteFontCollectionLoader",
+    "IDWriteFontFace",
+    "IDWriteFontFaceReference",
+    "IDWriteFontFamily",
+    "IDWriteFontFile",
+    "IDWriteFontFileEnumerator",
+    "IDWriteFontFileLoader",
+    "IDWriteFontList",
+    "IDWriteFontSet",
+    "IDWriteFontSetBuilder",
+    "IDWriteGdiInterop",
+    "IDWriteLocalFontFileLoader",
+    "IDWriteLocalizedStrings"
+]
 
 
 class DWRITE_FACTORY_TYPE(IntEnum):
@@ -34,6 +59,42 @@ class DWRITE_LOCALITY(IntEnum):
     DWRITE_LOCALITY_LOCAL = 2
 
 
+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 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 IDWriteFontFileLoader(IUnknown):
     # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontfileloader
     _iid_ = GUID("{727cad4e-d6af-4c9e-8a08-d695b11caa49}")
@@ -43,7 +104,7 @@ class IDWriteFontFileLoader(IUnknown):
 
 
 class IDWriteLocalFontFileLoader(IDWriteFontFileLoader):
-    # https://learn.microsoft.com/en-us/windows/win32/directwrite/idwritelocalfontfileloader
+    # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritelocalfontfileloader
     _iid_ = GUID("{b2d9f3ec-c9fe-4a11-a2ec-d86208f7c0a2}")
     _methods_ = [
         STDMETHOD(HRESULT, "GetFilePathLengthFromKey", [wintypes.LPCVOID, wintypes.UINT, POINTER(wintypes.UINT)]),
@@ -62,6 +123,19 @@ class IDWriteFontFile(IUnknown):
     ]
 
 
+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 IDWriteFontFaceReference(IUnknown):
     # https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/nn-dwrite_3-idwritefontfacereference
     _iid_ = GUID("{5E7FA7CA-DDE3-424C-89F0-9FCD6FED58CD}")
@@ -132,7 +206,7 @@ class IDWriteFont(IUnknown):
         STDMETHOD(None, "GetStyle"),  # Need to be implemented
         STDMETHOD(None, "IsSymbolFont"),  # Need to be implemented
         STDMETHOD(None, "GetFaceNames"),  # Need to be implemented
-        STDMETHOD(None, "GetInformationalStrings"),  # Need to be implemented
+        STDMETHOD(HRESULT, "GetInformationalStrings", [wintypes.UINT, POINTER(POINTER(IDWriteLocalizedStrings)), POINTER(wintypes.BOOL)]),
         STDMETHOD(None, "GetSimulations"),  # Need to be implemented
         STDMETHOD(None, "GetMetrics"),  # Need to be implemented
         STDMETHOD(None, "HasCharacter"),  # Need to be implemented
@@ -167,29 +241,55 @@ class IDWriteFontCollection(IUnknown):
         STDMETHOD(wintypes.UINT, "GetFontFamilyCount"),
         STDMETHOD(HRESULT, "GetFontFamily", [wintypes.UINT, POINTER(POINTER(IDWriteFontFamily))]),
         STDMETHOD(None, "FindFamilyName"),  # Need to be implemented
-        STDMETHOD(None, "GetFontFromFontFace"),  # Need to be implemented
+        STDMETHOD(HRESULT, "GetFontFromFontFace", [POINTER(IDWriteFontFace), POINTER(POINTER(IDWriteFont))]),
+    ]
+
+
+class IDWriteGdiInterop(IUnknown):
+    # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritegdiinterop
+    _iid_ = GUID("{1edd9491-9853-4299-898f-6432983b6f3a}")
+    _methods_ = [
+        STDMETHOD(HRESULT, "CreateFontFromLOGFONT1", [POINTER(LOGFONTW), POINTER(POINTER(IDWriteFont))]),
+        STDMETHOD(None, "ConvertFontToLOGFONT"),  # Need to be implemented
+        STDMETHOD(None, "ConvertFontFaceToLOGFONT"),  # Need to be implemented
+        STDMETHOD(HRESULT, "CreateFontFaceFromHdc", [wintypes.HDC, POINTER(POINTER(IDWriteFontFace))]),
+        STDMETHOD(None, "CreateBitmapRenderTarget"),  # Need to be implemented
     ]
 
 
+class IDWriteFontFileEnumerator(IUnknown):
+    # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontfileenumerator
+    _iid_ = GUID("{72755049-5ff7-435d-8348-4be97cfa6c7c}")
+    _methods_ = [
+        STDMETHOD(HRESULT, "MoveNext", [POINTER(wintypes.BOOL)]),
+        STDMETHOD(HRESULT, "GetCurrentFontFile", [POINTER(POINTER(IDWriteFontFile))]),
+    ]
+
+
+class IDWriteFontCollectionLoader(IUnknown):
+    # https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontcollectionloader
+    _iid_ = GUID("{cca920e4-52f0-492b-bfa8-29c72ee0a468}")
+
+
 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, "GetSystemFontCollection", [POINTER(POINTER(IDWriteFontCollection)), wintypes.BOOLEAN]),
-        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(HRESULT, "CreateCustomFontCollection", [POINTER(IDWriteFontCollectionLoader), wintypes.LPVOID, wintypes.UINT, POINTER(POINTER(IDWriteFontCollection))]),
+        STDMETHOD(HRESULT, "RegisterFontCollectionLoader", [POINTER(IDWriteFontCollectionLoader)]),
+        STDMETHOD(HRESULT, "UnregisterFontCollectionLoader", [POINTER(IDWriteFontCollectionLoader)]),
+        STDMETHOD(HRESULT, "CreateFontFileReference", [POINTER(wintypes.WCHAR), POINTER(wintypes.FILETIME), POINTER(POINTER(IDWriteFontFile))]),
+        STDMETHOD(HRESULT, "CreateCustomFontFileReference", [wintypes.LPVOID, wintypes.UINT, POINTER(IDWriteFontFileLoader), POINTER(POINTER(IDWriteFontFile))]),
+        STDMETHOD(HRESULT, "CreateFontFace", [wintypes.UINT, wintypes.UINT, POINTER(POINTER(IDWriteFontFile)), wintypes.UINT, wintypes.UINT, POINTER(POINTER(IDWriteFontFace))]),
         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(HRESULT, "RegisterFontFileLoader", [POINTER(IDWriteFontFileLoader)]),
+        STDMETHOD(HRESULT, "UnregisterFontFileLoader", [POINTER(IDWriteFontFileLoader)]),
         STDMETHOD(None, "CreateTextFormat"),  # Need to be implemented
         STDMETHOD(None, "CreateTypography"),  # Need to be implemented
-        STDMETHOD(None, "GetGdiInterop"),  # Need to be implemented
+        STDMETHOD(HRESULT, "GetGdiInterop", [POINTER(POINTER(IDWriteGdiInterop))]),
         STDMETHOD(None, "CreateTextLayout"),  # Need to be implemented
         STDMETHOD(None, "CreateGdiCompatibleTextLayout"),  # Need to be implemented
         STDMETHOD(None, "CreateEllipsisTrimmingSign"),  # Need to be implemented
@@ -198,6 +298,10 @@ class IDWriteFactory(IUnknown):
         STDMETHOD(None, "CreateGlyphRunAnalysis"),  # Need to be implemented
     ]
 
+IDWriteFontCollectionLoader._methods_ = [
+    STDMETHOD(HRESULT, "CreateEnumeratorFromKey", [POINTER(IDWriteFactory), wintypes.LPVOID, wintypes.UINT, POINTER(POINTER(IDWriteFontFileEnumerator))]),
+]
+
 
 class IDWriteFactory1(IDWriteFactory):
     # https://learn.microsoft.com/en-us/windows/win32/api/dwrite_1/nn-dwrite_1-idwritefactory1
@@ -236,204 +340,11 @@ class IDWriteFactory3(IDWriteFactory2):
     ]
 
 
-class WindowsFonts(SystemFonts):
-    _DWriteCreateFactory = None
-    VALID_FONT_FORMATS = [
-        DWRITE_FONT_FILE_TYPE.DWRITE_FONT_FILE_TYPE_CFF,
-        DWRITE_FONT_FILE_TYPE.DWRITE_FONT_FILE_TYPE_TRUETYPE,
-        DWRITE_FONT_FILE_TYPE.DWRITE_FONT_FILE_TYPE_OPENTYPE_COLLECTION,
-        DWRITE_FONT_FILE_TYPE.DWRITE_FONT_FILE_TYPE_TRUETYPE_COLLECTION,
-    ]
+class DWrite:
+    def __init__(self) -> None:
+        dwrite = windll.dwrite
 
-    def get_system_fonts_filename() -> Set[str]:
-        windows_version = getwindowsversion()
-
-        if WindowsVersionHelpers.is_windows_10_or_greater(windows_version):
-            fonts_filename = WindowsFonts._get_fonts_filename_windows_10_or_more()
-        elif WindowsVersionHelpers.is_windows_vista_sp2_or_greater(windows_version):
-            fonts_filename = WindowsFonts._get_fonts_filename_windows_vista_sp2_or_more()
-        else:
-            raise OSNotSupported("FindSystemFontsFilename only works on Windows Vista SP2 or more")
-
-        return fonts_filename
-
-    @staticmethod
-    def _get_fonts_filename_windows_10_or_more() -> Set[str]:
-        """
-        Return an set of all the installed fonts filename.
-        """
-        if WindowsFonts._DWriteCreateFactory is None:
-            WindowsFonts._load_DWriteCreateFactory()
-
-        fonts_filename = set()
-
-        dwrite_factory = POINTER(IDWriteFactory3)()
-        WindowsFonts._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))
-
-            locality = font_face_reference.GetLocality()
-            if DWRITE_LOCALITY(locality) != DWRITE_LOCALITY.DWRITE_LOCALITY_LOCAL:
-                continue
-
-            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))
-
-            # For a user, even if IDWriteFontFaceReference::GetLocality returned DWRITE_LOCALITY_LOCAL,
-            # the QueryInterface always fails for one specific font.
-            # I did a bunch of tests with him to try to understand why it fails.
-            # Here are my conclusions:
-            # First, with IDWriteFontFace3::GetInformationalStrings, we found the family name of the problematic font. It was "Levenim MT".
-            # Secondly, WindowsFonts._get_fonts_filename_windows_vista_sp2_or_more doesn't list "Levenim MT",
-            # so QueryInterface doesn't raise an exception.
-            # Thirdly, EnumFontFamiliesEx didn't enumerate "Levenim MT". Also, TextOut also doesn't display the font.
-            # Fourthly, if we search "Levenim MT" with IDWriteFontSet::GetMatchingFonts, the font is not found.
-            # Fifthly, the font doesn't show up in "C:\Windows\Fonts".
-            # Sixthly, in Word, Aegisub, Paint.NET, libass, and VSFilter the font doesn't show up.
-            # Finally, with IDWriteFontFileStream, we have been able to get the file.
-            #   So the font should be physically on the hard drive, but we couldn't find it.
-            #   If we are able to get the data of the font file, why can't we display the font in any software?
-            # This seems to be a DirectWrite bug. To bypass it, if QueryInterface fails, ignore the font.
-            # Anyways, the font cannot be displayed, so it doesn't matter.
-            try:
-                local_loader = loader.QueryInterface(IDWriteLocalFontFileLoader)
-            except 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))
-
-            font_filename = buffer.value
-            if isfile(font_filename):
-                is_supported_font_type = wintypes.BOOLEAN()
-                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))
-
-                if DWRITE_FONT_FILE_TYPE(font_file_type.value) in WindowsFonts.VALID_FONT_FORMATS:
-                    fonts_filename.add(buffer.value)
-
-        return fonts_filename
-
-    @staticmethod
-    def _get_fonts_filename_windows_vista_sp2_or_more() -> Set[str]:
-        """
-        Return an set of all the installed fonts filename.
-        """
-        if WindowsFonts._DWriteCreateFactory is None:
-            WindowsFonts._load_DWriteCreateFactory()
-
-        fonts_filename = set()
-
-        dwrite_factory = POINTER(IDWriteFactory)()
-        WindowsFonts._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()):
-                try:
-                    font = POINTER(IDWriteFont)()
-                    family.GetFont(j, byref(font))
-                except COMError:
-                    # If the file doesn't exist, DirectWrite raise an exception
-                    continue
-
-                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))
-
-                    local_loader = loader.QueryInterface(IDWriteLocalFontFileLoader)
-
-                    is_supported_font_type = wintypes.BOOLEAN()
-                    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))
-
-                    if DWRITE_FONT_FILE_TYPE(font_file_type.value) not in WindowsFonts.VALID_FONT_FORMATS:
-                        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_filename.add(buffer.value)
-
-        return fonts_filename
-
-    @staticmethod
-    def _load_DWriteCreateFactory():
-        WindowsFonts._DWriteCreateFactory = windll.dwrite.DWriteCreateFactory
-        WindowsFonts._DWriteCreateFactory.restype = HRESULT
-        WindowsFonts._DWriteCreateFactory.argtypes = [wintypes.UINT, GUID, POINTER(POINTER(IUnknown))]
-
-
-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_10_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, 10240)
+        # 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))]
diff --git a/find_system_fonts_filename/windows/gdi32.py b/find_system_fonts_filename/windows/gdi32.py
new file mode 100644
index 0000000..8db73cc
--- /dev/null
+++ b/find_system_fonts_filename/windows/gdi32.py
@@ -0,0 +1,151 @@
+from ctypes import POINTER, WINFUNCTYPE, c_ubyte, Structure, windll, wintypes
+from ..exceptions import SystemApiError
+
+__all__ = ["GDI32"]
+
+
+class LOGFONTW(Structure):
+    # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-logfontw
+    _fields_ = [
+        ("lfHeight", wintypes.LONG),
+        ("lfWidth", wintypes.LONG),
+        ("lfEscapement", wintypes.LONG),
+        ("lfOrientation", wintypes.LONG),
+        ("lfWeight", wintypes.LONG),
+        ("lfItalic", c_ubyte), # Cannot use wintypes.BYTE on old version of python, see https://github.com/python/cpython/issues/60580
+        ("lfUnderline", c_ubyte),
+        ("lfStrikeOut", c_ubyte),
+        ("lfCharSet", c_ubyte),
+        ("lfOutPrecision", c_ubyte),
+        ("lfClipPrecision", c_ubyte),
+        ("lfQuality", c_ubyte),
+        ("lfPitchAndFamily", c_ubyte),
+        ("lfFaceName", wintypes.WCHAR * 32),
+    ]
+
+
+class TEXTMETRICW(Structure):
+    # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-textmetricw
+    _fields_ = [
+        ('tmHeight', wintypes.LONG),
+        ('tmAscent', wintypes.LONG),
+        ('tmDescent', wintypes.LONG),
+        ('tmInternalLeading', wintypes.LONG),
+        ('tmExternalLeading', wintypes.LONG),
+        ('tmAveCharWidth', wintypes.LONG),
+        ('tmMaxCharWidth', wintypes.LONG),
+        ('tmWeight', wintypes.LONG),
+        ('tmOverhang', wintypes.LONG),
+        ('tmDigitizedAspectX', wintypes.LONG),
+        ('tmDigitizedAspectY', wintypes.LONG),
+        ('tmFirstChar', wintypes.WCHAR),
+        ('tmLastChar', wintypes.WCHAR),
+        ('tmDefaultChar', wintypes.WCHAR),
+        ('tmBreakChar', wintypes.WCHAR),
+        ('tmItalic', c_ubyte),
+        ('tmUnderlined', c_ubyte),
+        ('tmStruckOut', c_ubyte),
+        ('tmPitchAndFamily', c_ubyte),
+        ('tmCharSet', c_ubyte)
+    ]
+
+
+class ENUMLOGFONTEXW(Structure):
+    # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-enumlogfontexw
+    _fields_ = [
+        ("elfLogFont", LOGFONTW),
+        ("elfFullName", wintypes.WCHAR * 64),
+        ("elfStyle", wintypes.WCHAR * 32),
+        ("elfScript", wintypes.WCHAR * 32),
+    ]
+
+
+class GDI32:
+    def __init__(self) -> None:
+        gdi = windll.gdi32
+
+        self.LF_FACESIZE = 32
+        self.RASTER_FONTTYPE = 0x0001
+        self.DEVICE_FONTTYPE = 0x0002
+        self.TRUETYPE_FONTTYPE = 0x0004
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-addfontresourcew
+        self.AddFontResourceW = gdi.AddFontResourceW
+        self.AddFontResourceW.restype = wintypes.INT
+        self.AddFontResourceW.argtypes = [wintypes.LPCWSTR]
+        self.AddFontResourceW.errcheck = self.errcheck_is_result_0_or_null
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-removefontresourcew
+        self.RemoveFontResourceW = gdi.RemoveFontResourceW
+        self.RemoveFontResourceW.restype = wintypes.BOOL
+        self.RemoveFontResourceW.argtypes = [wintypes.LPCWSTR]
+        self.RemoveFontResourceW.errcheck = self.errcheck_is_result_0_or_null
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createcompatibledc
+        self.CreateCompatibleDC = gdi.CreateCompatibleDC
+        self.CreateCompatibleDC.restype = wintypes.HDC
+        self.CreateCompatibleDC.argtypes = [wintypes.HDC]
+        self.CreateCompatibleDC.errcheck = self.errcheck_is_result_0_or_null
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-deletedc
+        self.DeleteDC = gdi.DeleteDC
+        self.DeleteDC.restype = wintypes.BOOL
+        self.DeleteDC.argtypes = [wintypes.HDC]
+        self.DeleteDC.errcheck = self.errcheck_is_result_0_or_null
+
+        # https://learn.microsoft.com/en-us/previous-versions/dd162618(v=vs.85)
+        self.ENUMFONTFAMEXPROC = WINFUNCTYPE(
+            wintypes.INT,
+            ENUMLOGFONTEXW,
+            TEXTMETRICW,
+            wintypes.DWORD,
+            wintypes.LPARAM,
+        )
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-enumfontsw
+        self.EnumFontsW = gdi.EnumFontsW
+        self.EnumFontsW.restype = wintypes.INT
+        self.EnumFontsW.argtypes = [wintypes.HDC, wintypes.LPCWSTR, self.ENUMFONTFAMEXPROC, wintypes.LPARAM]
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-enumfontfamiliesw
+        self.EnumFontFamiliesW = gdi.EnumFontFamiliesW
+        self.EnumFontFamiliesW.restype = wintypes.INT
+        self.EnumFontFamiliesW.argtypes = [wintypes.HDC, wintypes.LPCWSTR, self.ENUMFONTFAMEXPROC, wintypes.LPARAM]
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-enumfontfamiliesexw
+        self.EnumFontFamiliesExW = gdi.EnumFontFamiliesExW
+        self.EnumFontFamiliesExW.restype = wintypes.INT
+        self.EnumFontFamiliesExW.argtypes = [wintypes.HDC, POINTER(LOGFONTW), self.ENUMFONTFAMEXPROC, wintypes.LPARAM, wintypes.DWORD]
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createfontindirectw
+        self.CreateFontIndirectW = gdi.CreateFontIndirectW
+        self.CreateFontIndirectW.restype = wintypes.HFONT
+        self.CreateFontIndirectW.argtypes = [POINTER(LOGFONTW)]
+        self.CreateFontIndirectW.errcheck = self.errcheck_is_result_0_or_null
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-selectobject
+        self.SelectObject = gdi.SelectObject
+        self.SelectObject.restype = wintypes.HGDIOBJ
+        self.SelectObject.argtypes = [wintypes.HDC, wintypes.HGDIOBJ]
+        self.SelectObject.errcheck = self.has_SelectObject_failed
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-deleteobject
+        self.DeleteObject = gdi.DeleteObject
+        self.DeleteObject.restype = wintypes.BOOL
+        self.DeleteObject.argtypes = [wintypes.HGDIOBJ]
+        self.DeleteObject.errcheck = self.errcheck_is_result_0_or_null
+
+
+    @staticmethod
+    def errcheck_is_result_0_or_null(result, func, args):
+        if not result:
+            raise SystemApiError(f"{func.__name__} fails. The result is {result} which is invalid")
+        return result
+
+
+    @staticmethod
+    def has_SelectObject_failed(result, func, args):
+        HGDI_ERROR = wintypes.HGDIOBJ(0xFFFFFFFF)
+        if result == None or result == HGDI_ERROR:
+            raise SystemApiError(f"{func.__name__} fails. The result is {result} which is invalid")
+        return result
diff --git a/find_system_fonts_filename/windows/kernel32.py b/find_system_fonts_filename/windows/kernel32.py
new file mode 100644
index 0000000..6d928a6
--- /dev/null
+++ b/find_system_fonts_filename/windows/kernel32.py
@@ -0,0 +1,23 @@
+from ctypes import windll, wintypes
+from ..exceptions import SystemApiError
+
+__all__ = ["Kernel32"]
+
+
+class Kernel32():
+    def __init__(self) -> None:
+        kernel32 = windll.kernel32
+
+        self.LOCALE_NAME_MAX_LENGTH = 85
+
+        # 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_or_null
+
+    @staticmethod
+    def errcheck_is_result_0_or_null(result, func, args):
+        if not result:
+            raise SystemApiError(f"{func.__name__} fails. The result is {result} which is invalid")
+        return result
diff --git a/find_system_fonts_filename/windows/msvcrt.py b/find_system_fonts_filename/windows/msvcrt.py
new file mode 100644
index 0000000..e57d661
--- /dev/null
+++ b/find_system_fonts_filename/windows/msvcrt.py
@@ -0,0 +1,24 @@
+from ctypes import c_size_t, windll, wintypes
+from ..exceptions import SystemApiError
+
+__all__ = ["MSVCRT"]
+
+
+class MSVCRT:
+    def __init__(self) -> None:
+        msvcrt = windll.msvcrt
+
+        # https://learn.microsoft.com/fr-fr/cpp/c-runtime-library/reference/strncpy-s-strncpy-s-l-wcsncpy-s-wcsncpy-s-l-mbsncpy-s-mbsncpy-s-l?view=msvc-170
+        self.wcsncpy_s = msvcrt.wcsncpy_s
+        self.wcsncpy_s.restype = wintypes.INT
+        self.wcsncpy_s.argtypes = [wintypes.LPWSTR, c_size_t, wintypes.LPCWSTR, c_size_t]
+        self.wcsncpy_s.errcheck = self.errcheck_wcsncpy_s
+
+        self.TRUNCATE = c_size_t(-1).value
+
+    @staticmethod
+    def errcheck_wcsncpy_s(result, func, args):
+        STRUNCATE = 80
+        if result not in (0, STRUNCATE):
+            raise SystemApiError(f"{func.__name__} fails. The result is {result} which is invalid")
+        return result
diff --git a/find_system_fonts_filename/windows/user32.py b/find_system_fonts_filename/windows/user32.py
new file mode 100644
index 0000000..04bbfc2
--- /dev/null
+++ b/find_system_fonts_filename/windows/user32.py
@@ -0,0 +1,24 @@
+from ctypes import windll, wintypes
+from ..exceptions import SystemApiError
+
+__all__ = ["User32"]
+
+
+class User32():
+    def __init__(self) -> None:
+        user32 = windll.user32
+
+        self.HWND_BROADCAST = wintypes.HWND(0xFFFF)
+        self.WM_FONTCHANGE = wintypes.UINT(0x001D)
+
+        # https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendnotifymessagew
+        self.SendNotifyMessageW = user32.SendNotifyMessageW
+        self.SendNotifyMessageW.restype = wintypes.BOOL
+        self.SendNotifyMessageW.argtypes = [wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM]
+        self.SendNotifyMessageW.errcheck = self.errcheck_is_result_0_or_null
+
+    @staticmethod
+    def errcheck_is_result_0_or_null(result, func, args):
+        if not result:
+            raise SystemApiError(f"{func.__name__} fails. The result is {result} which is invalid")
+        return result
diff --git a/find_system_fonts_filename/windows/version_helpers.py b/find_system_fonts_filename/windows/version_helpers.py
new file mode 100644
index 0000000..0e709ee
--- /dev/null
+++ b/find_system_fonts_filename/windows/version_helpers.py
@@ -0,0 +1,37 @@
+__all__ = [
+    "WindowsVersionHelpers"
+]
+
+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_10_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, 10240)
diff --git a/find_system_fonts_filename/windows/windows_fonts.py b/find_system_fonts_filename/windows/windows_fonts.py
new file mode 100644
index 0000000..dbab9aa
--- /dev/null
+++ b/find_system_fonts_filename/windows/windows_fonts.py
@@ -0,0 +1,354 @@
+from .advapi32 import Advapi32, RegistryDataType
+from .dwrite import (
+    DWrite,
+    DWRITE_FACTORY_TYPE,
+    DWRITE_FONT_FILE_TYPE,
+    DWRITE_FONT_SIMULATIONS,
+    DWRITE_INFORMATIONAL_STRING_ID,
+    IDWriteFactory,
+    IDWriteFont,
+    IDWriteFontCollection,
+    IDWriteFontCollectionLoader,
+    IDWriteFontFace,
+    IDWriteFontFile,
+    IDWriteFontFileEnumerator,
+    IDWriteFontFileLoader,
+    IDWriteGdiInterop,
+    IDWriteLocalFontFileLoader,
+    IDWriteLocalizedStrings
+)
+from .gdi32 import GDI32, ENUMLOGFONTEXW, TEXTMETRICW
+from .kernel32 import Kernel32
+from .msvcrt import MSVCRT
+from .user32 import User32
+from .version_helpers import WindowsVersionHelpers
+from comtypes import COMObject
+from ctypes import addressof, byref, cast, create_unicode_buffer, POINTER, py_object, sizeof, wintypes
+from pathlib import Path
+from sys import getwindowsversion
+from typing import List, Set
+from ..exceptions import NotSupportedFontFile, OSNotSupported, SystemApiError
+from ..system_fonts import SystemFonts
+
+__all__ = ["WindowsFonts"]
+
+
+def get_filepath_from_IDWriteFontFace(font_face) -> Set[str]:
+    fonts_filename: Set[str] = set()
+
+    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.BOOLEAN()
+            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))
+
+            if DWRITE_FONT_FILE_TYPE(font_file_type.value) not in WindowsFonts.VALID_FONT_FORMATS:
+                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_filename.add(buffer.value)
+
+    return fonts_filename
+
+
+def enum_fonts_2(logfont: ENUMLOGFONTEXW, text_metric: TEXTMETRICW, font_type: wintypes.DWORD, lparam: wintypes.LPARAM):
+    enum_data: EnumData = py_object.from_address(lparam).value
+
+    # It seems that font_type can be 0. In those case, the font format is .fon
+    # We also discard RASTER_FONTTYPE which are bitmap font
+    if not (font_type & enum_data.gdi.RASTER_FONTTYPE) and font_type:
+        # Replace the lfFaceName with the elfFullName.
+        # See why here: https://github.com/libass/libass/issues/744
+        lfFaceName = create_unicode_buffer(enum_data.gdi.LF_FACESIZE)
+        enum_data.msvcrt.wcsncpy_s(lfFaceName, enum_data.gdi.LF_FACESIZE, logfont.elfFullName, enum_data.msvcrt.TRUNCATE)
+        logfont.elfLogFont.lfFaceName = lfFaceName.value
+
+        hfont = enum_data.gdi.CreateFontIndirectW(byref(logfont.elfLogFont))
+        enum_data.gdi.SelectObject(enum_data.dc, hfont)
+
+        font_face = POINTER(IDWriteFontFace)()
+        enum_data.gdi_interop.CreateFontFaceFromHdc(enum_data.dc, byref(font_face))
+        enum_data.fonts_filename.update(get_filepath_from_IDWriteFontFace(font_face))
+
+        enum_data.gdi.DeleteObject(hfont)
+
+    return True
+
+
+def enum_fonts_1(logfont: ENUMLOGFONTEXW, text_metric: TEXTMETRICW, font_type: wintypes.DWORD, lparam: wintypes.LPARAM):
+    enum_data: EnumData = py_object.from_address(lparam).value
+    enum_data.gdi.EnumFontFamiliesW(enum_data.dc, logfont.elfLogFont.lfFaceName, enum_data.gdi.ENUMFONTFAMEXPROC(enum_fonts_2), lparam)
+
+    return True
+
+
+class EnumData:
+    def __init__(self, gdi: GDI32, msvcrt: MSVCRT, dc: wintypes.HDC, fonts_filename: Set[str], gdi_interop: POINTER(IDWriteGdiInterop)):
+        self.gdi = gdi
+        self.msvcrt = msvcrt
+        self.dc = dc
+        self.fonts_filename = fonts_filename
+        self.gdi_interop = gdi_interop
+
+
+class WindowsFonts(SystemFonts):
+    VALID_FONT_FORMATS = [
+        DWRITE_FONT_FILE_TYPE.DWRITE_FONT_FILE_TYPE_CFF,
+        DWRITE_FONT_FILE_TYPE.DWRITE_FONT_FILE_TYPE_TRUETYPE,
+        DWRITE_FONT_FILE_TYPE.DWRITE_FONT_FILE_TYPE_OPENTYPE_COLLECTION,
+        DWRITE_FONT_FILE_TYPE.DWRITE_FONT_FILE_TYPE_TRUETYPE_COLLECTION,
+    ]
+
+    def get_system_fonts_filename() -> Set[str]:
+        windows_version = getwindowsversion()
+
+        if not WindowsVersionHelpers.is_windows_vista_sp2_or_greater(windows_version):
+            raise OSNotSupported("FindSystemFontsFilename only works on Windows Vista SP2 or more")
+
+        dwrite = DWrite()
+        gdi = GDI32()
+        msvcrt = MSVCRT()
+        fonts_filename = set()
+
+        dwrite_factory = POINTER(IDWriteFactory)()
+        dwrite.DWriteCreateFactory(DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_ISOLATED, dwrite_factory._iid_, byref(dwrite_factory))
+
+        gdi_interop = POINTER(IDWriteGdiInterop)()
+        dwrite_factory.GetGdiInterop(byref(gdi_interop))
+
+        dc = gdi.CreateCompatibleDC(None)
+
+        enum_data = EnumData(gdi, msvcrt, dc, fonts_filename, gdi_interop)
+        object_enum_data = py_object(enum_data)
+
+        # See this link to understand why we do call EnumFontFamiliesW and then a EnumFontFamiliesW
+        # and not directly EnumFontFamiliesExW.
+        # https://stackoverflow.com/a/62405274/15835974
+        enum_data.gdi.EnumFontFamiliesW(enum_data.dc, None, enum_data.gdi.ENUMFONTFAMEXPROC(enum_fonts_1), addressof(object_enum_data))
+
+        gdi.DeleteDC(dc)
+
+        return fonts_filename
+
+
+    @staticmethod
+    def get_registry_font_name(font_filename: Path) -> str:
+        dwrite = DWrite()
+        kernel32 = Kernel32()
+        font_filename_buffer = create_unicode_buffer(str(font_filename))
+
+        dwrite_factory = POINTER(IDWriteFactory)()
+        dwrite.DWriteCreateFactory(DWRITE_FACTORY_TYPE.DWRITE_FACTORY_TYPE_ISOLATED, IDWriteFactory._iid_, byref(dwrite_factory))
+
+        font_file = POINTER(IDWriteFontFile)()
+        dwrite_factory.CreateFontFileReference(font_filename_buffer, None, byref(font_file))
+
+        is_supported_font_type = wintypes.BOOLEAN()
+        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))
+
+        if not is_supported_font_type:
+            raise NotSupportedFontFile(f"The font file \"{font_filename}\" isn't supported on Windows.")
+
+        font_collection_loader = CustomFontCollectionLoader([font_filename])
+        dwrite_factory.RegisterFontCollectionLoader(font_collection_loader)
+
+        custom_collection = POINTER(IDWriteFontCollection)()
+        font_loader_key = create_unicode_buffer("find_system_fonts_filename_collection_loader")
+        dwrite_factory.CreateCustomFontCollection(font_collection_loader,
+                                                    cast(font_loader_key, wintypes.LPVOID),
+                                                    sizeof(font_loader_key),
+                                                    byref(custom_collection))
+
+        full_names: List[str] = []
+        for i in range(number_of_faces.value):
+            font_face = POINTER(IDWriteFontFace)()
+            dwrite_factory.CreateFontFace(font_face_type.value, 1, byref(font_file), i, DWRITE_FONT_SIMULATIONS.DWRITE_FONT_SIMULATIONS_NONE, byref(font_face))
+
+            """
+            Converting a IDWriteFontFace to a IDWriteFont isn't easy.
+            We can't use IDWriteGdiInterop and use ConvertFontFaceToLOGFONT and then CreateFontFromLOGFONT,
+            because it needs to have the font installed in the system and since we just called AddFontResourceW,
+            the font may not be available. See issue #14
+            """
+            font = POINTER(IDWriteFont)()
+            custom_collection.GetFontFromFontFace(font_face, byref(font))
+
+            full_name = POINTER(IDWriteLocalizedStrings)()
+            exists = wintypes.BOOL()
+            font.GetInformationalStrings(
+                DWRITE_INFORMATIONAL_STRING_ID.DWRITE_INFORMATIONAL_STRING_FULL_NAME,
+                byref(full_name),
+                byref(exists)
+            )
+
+            if not exists:
+                raise SystemApiError("Could not fetch the font name")
+
+            # Based on https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-findlocalename#remarks
+            locale_name = create_unicode_buffer(kernel32.LOCALE_NAME_MAX_LENGTH)
+            kernel32.GetUserDefaultLocaleName(locale_name, kernel32.LOCALE_NAME_MAX_LENGTH)
+
+            index = wintypes.UINT()
+            exists = wintypes.BOOL()
+            full_name.FindLocaleName(locale_name, byref(index), byref(exists))
+
+            if not exists.value:
+                full_name.FindLocaleName("en-us", byref(index), byref(exists))
+
+            if not exists.value:
+                index = 0
+
+            length = wintypes.UINT()
+            full_name.GetStringLength(index, byref(length))
+
+            family_names_buffer = create_unicode_buffer(length.value + 1)
+            full_name.GetString(index, family_names_buffer, len(family_names_buffer))
+
+            full_names.append(family_names_buffer.value)
+
+        registry_font_name = " & ".join(full_names) + " (FindSystemFontsFilename)"
+        dwrite_factory.UnregisterFontCollectionLoader(font_collection_loader)
+
+        return registry_font_name
+
+
+    def install_font(font_filename: Path, add_font_to_registry: bool) -> None:
+        windows_version = getwindowsversion()
+
+        if not WindowsVersionHelpers.is_windows_vista_sp2_or_greater(windows_version):
+            raise OSNotSupported("FindSystemFontsFilename only works on Windows Vista SP2 or more")
+
+        # Font in the registry have been added in 10.0.17083.
+        # Source: https://superuser.com/a/1658749/1729132
+        is_build_17083_or_greater = WindowsVersionHelpers.is_windows_version_or_greater(windows_version, 10, 0, 17083)
+
+        gdi = GDI32()
+        user32 = User32()
+
+        font_filename_buffer = create_unicode_buffer(str(font_filename))
+
+        gdi.AddFontResourceW(font_filename_buffer)
+        if add_font_to_registry and is_build_17083_or_greater:
+            advapi32 = Advapi32()
+            hkey = wintypes.HKEY()
+            registry_font_name = WindowsFonts.get_registry_font_name(font_filename)
+
+            advapi32.RegOpenKeyExW(advapi32.HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts", 0, advapi32.KEY_SET_VALUE, byref(hkey))
+            advapi32.RegSetValueExW(hkey, registry_font_name, 0, RegistryDataType.REG_SZ.value, cast(font_filename_buffer, wintypes.LPBYTE), sizeof(font_filename_buffer))
+            advapi32.RegCloseKey(hkey)
+
+        user32.SendNotifyMessageW(user32.HWND_BROADCAST, user32.WM_FONTCHANGE, 0, 0)
+
+
+    def uninstall_font(font_filename: Path, added_font_to_registry: bool) -> None:
+        windows_version = getwindowsversion()
+
+        if not WindowsVersionHelpers.is_windows_vista_sp2_or_greater(windows_version):
+            raise OSNotSupported("FindSystemFontsFilename only works on Windows Vista SP2 or more")
+
+        # Font in the registry have been added in 10.0.17083.
+        # Source: https://superuser.com/a/1658749/1729132
+        is_build_17083_or_greater = WindowsVersionHelpers.is_windows_version_or_greater(windows_version, 10, 0, 17083)
+
+        gdi = GDI32()
+        user32 = User32()
+
+        if added_font_to_registry and is_build_17083_or_greater:
+            advapi32 = Advapi32()
+            hkey = wintypes.HKEY()
+            registry_font_name = WindowsFonts.get_registry_font_name(font_filename)
+
+            advapi32.RegOpenKeyExW(advapi32.HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts", 0, advapi32.KEY_SET_VALUE, byref(hkey))
+            advapi32.RegDeleteValueW(hkey, registry_font_name)
+            advapi32.RegCloseKey(hkey)
+
+        # When a font have been installed multiple time,
+        # we need to call RemoveFontResourceW until it fails.
+        # Also, when the font have been added to the registry,
+        # RemoveFontResourceW fails, but in reality, it actually uninstalled
+        # the font, so let's always ignore any error it returns.
+        while True:
+            try:
+                gdi.RemoveFontResourceW(str(font_filename))
+            except SystemApiError:
+                break
+
+        user32.SendNotifyMessageW(user32.HWND_BROADCAST, user32.WM_FONTCHANGE, 0, 0)
+
+
+class CustomFontFileEnumerator(COMObject):
+    _com_interfaces_ = [IDWriteFontFileEnumerator]
+
+    def __init__(self, dwrite_factory: POINTER(IDWriteFactory), font_files_path: List[Path]):
+        super(CustomFontFileEnumerator, self).__init__()
+        self.dwrite_factory = dwrite_factory
+        self.font_files_path = font_files_path
+        self.current_index = -1
+        self.current_font_file = None
+
+    def IDWriteFontFileEnumerator_MoveNext(self, this, has_current_file: POINTER(wintypes.BOOL)) -> int:
+        self.current_index += 1
+        if self.current_index < len(self.font_files_path):
+            font_filename_buffer = create_unicode_buffer(str(self.font_files_path[self.current_index]))
+
+            font_file = POINTER(IDWriteFontFile)()
+            self.dwrite_factory.CreateFontFileReference(font_filename_buffer, None, byref(font_file))
+
+            self.current_font_file = font_file
+            has_current_file.contents.value = True
+        else:
+            has_current_file.contents.value = False
+        return 0 # S_OK
+
+    def IDWriteFontFileEnumerator_GetCurrentFontFile(self, this, font_file: POINTER(POINTER(IDWriteFontFile))) -> int:
+        if self.current_font_file:
+            font_file[0] = self.current_font_file
+            return 0 # S_OK
+        return 1 # S_FALSE
+
+
+class CustomFontCollectionLoader(COMObject):
+    _com_interfaces_ = [IDWriteFontCollectionLoader]
+
+    def __init__(self, font_files_path: List[Path]):
+        super(CustomFontCollectionLoader, self).__init__()
+        self.font_files_path = font_files_path
+
+    def IDWriteFontCollectionLoader_CreateEnumeratorFromKey(
+            self,
+            this,
+            factory: POINTER(IDWriteFactory),
+            collection_key: wintypes.LPVOID,
+            collection_key_size: wintypes.UINT,
+            font_file_enumerator: POINTER(POINTER(IDWriteFontFileEnumerator))
+        )-> int:
+
+        enum = CustomFontFileEnumerator(factory, self.font_files_path)
+        enumerator_ref = enum.QueryInterface(IDWriteFontFileEnumerator)
+        font_file_enumerator[0] = enumerator_ref
+
+        return 0 # S_OK
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..2d140b7
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,37 @@
+[project]
+name = "FindSystemFontsFilename"
+description = "Find the system fonts filename."
+authors = [
+    { name = "moi15moi", email = "moi15moismokerlolilol@gmail.com" },
+]
+license = { text = "MIT" }
+readme = "README.md"
+requires-python = ">=3.8"
+dependencies = [
+    "comtypes; platform_system=='Windows'",
+]
+classifiers=[
+        "Development Status :: 4 - Beta",
+        "Intended Audience :: Developers",
+        "Programming Language :: Python",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
+        "License :: OSI Approved :: MIT License",
+]
+dynamic = ["version"]
+
+[project.urls]
+Source = "https://github.com/moi15moi/FindSystemFontsFilename/"
+Tracker = "https://github.com/moi15moi/FindSystemFontsFilename/issues/"
+
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
+
+[tool.setuptools.dynamic]
+version = { attr = "find_system_fonts_filename.__init__.__version__" }
+
+[tool.setuptools.packages.find]
+include = ["find_system_fonts_filename*"]
\ No newline at end of file
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 0e4b2e9..0000000
--- a/setup.py
+++ /dev/null
@@ -1,51 +0,0 @@
-import os
-import re
-import setuptools
-
-
-here = os.path.abspath(os.path.dirname(__file__))
-
-
-def read(*parts):
-    with open(os.path.join(here, *parts), "r") as fp:
-        return fp.read()
-
-
-def find_version(*file_paths):
-    version_file = read(*file_paths)
-    version_match = re.search(r"^__version__ = [\'\"](.+)[\'\"]", version_file, re.M)
-    if version_match:
-        return version_match.group(1)
-    raise RuntimeError("Unable to find version string.")
-
-
-setuptools.setup(
-    name="FindSystemFontsFilename",
-    url="https://github.com/moi15moi/FindSystemFontsFilename/",
-    project_urls={
-        "Source": "https://github.com/moi15moi/FindSystemFontsFilename/",
-        "Tracker": "https://github.com/moi15moi/FindSystemFontsFilename/issues/",
-    },
-    author="moi15moi",
-    author_email="moi15moismokerlolilol@gmail.com",
-    description="Find the system fonts filename.",
-    long_description=open("README.md", encoding="utf-8").read(),
-    long_description_content_type="text/markdown",
-    version=find_version("find_system_fonts_filename", "__init__.py"),
-    packages=["find_system_fonts_filename"],
-    python_requires=">=3.8",
-    install_requires=[
-        "comtypes; platform_system=='Windows'",
-    ],
-    classifiers=[
-        "Development Status :: 4 - Beta",
-        "Intended Audience :: Developers",
-        "Programming Language :: Python",
-        "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.8",
-        "Programming Language :: Python :: 3.9",
-        "Programming Language :: Python :: 3.10",
-        "License :: OSI Approved :: MIT License",
-    ],
-    license="MIT",
-)


Test patch:
diff --git a/tests/SuperFunky-lgmWw.ttf b/tests/SuperFunky-lgmWw.ttf
new file mode 100644
index 0000000..b9a14a7
Binary files /dev/null and b/tests/SuperFunky-lgmWw.ttf differ
diff --git a/tests/test_fonts_filename.py b/tests/test_fonts_filename.py
index e8e98b1..67ffce9 100644
--- a/tests/test_fonts_filename.py
+++ b/tests/test_fonts_filename.py
@@ -1,6 +1,10 @@
+import pytest
+import sys
+from filecmp import cmp
+from os.path import dirname, isfile, join, realpath, samefile
 from pathlib import Path
-from find_system_fonts_filename import get_system_fonts_filename
-from os.path import isfile
+from platform import system
+from find_system_fonts_filename import get_system_fonts_filename, install_font, uninstall_font, OSNotSupported
 
 
 def test_get_system_fonts_filename():
@@ -21,3 +25,78 @@ def test_get_system_fonts_filename():
 
             signature = font_file.read(4)
             assert signature in [truetype_signature, opentype_signature, collection_signature]
+
+@pytest.mark.skipif(system() != 'Windows', reason="Test runs only on Windows")
+def test_install_uninstall_font_windows():
+
+    dir_path = dirname(realpath(__file__))
+    filename = Path(join(dir_path, "SuperFunky-lgmWw.ttf"))
+
+    fonts_filename = get_system_fonts_filename()
+    assert not any(samefile(filename, f) for f in fonts_filename)
+
+    install_font(filename, True)
+    fonts_filename = get_system_fonts_filename()
+    assert any(samefile(filename, f) for f in fonts_filename)
+
+    uninstall_font(filename, True)
+    fonts_filename = get_system_fonts_filename()
+    assert not any(samefile(filename, f) for f in fonts_filename)
+
+    install_font(filename, False)
+    fonts_filename = get_system_fonts_filename()
+    assert any(samefile(filename, f) for f in fonts_filename)
+
+    uninstall_font(filename, False)
+    fonts_filename = get_system_fonts_filename()
+    assert not any(samefile(filename, f) for f in fonts_filename)
+
+@pytest.mark.skipif(system() != 'Darwin', reason="Test runs only on Darwin")
+def test_install_uninstall_font_darwin():
+
+    dir_path = dirname(realpath(__file__))
+    filename = Path(join(dir_path, "SuperFunky-lgmWw.ttf"))
+
+    fonts_filename = get_system_fonts_filename()
+    assert not any(samefile(filename, f) for f in fonts_filename)
+
+    install_font(filename)
+    fonts_filename = get_system_fonts_filename()
+    assert any(samefile(filename, f) for f in fonts_filename)
+
+    uninstall_font(filename)
+    fonts_filename = get_system_fonts_filename()
+    assert not any(samefile(filename, f) for f in fonts_filename)
+
+@pytest.mark.skipif(not (system() == 'Linux' and not hasattr(sys, "getandroidapilevel")), reason="Test runs only on Linux")
+def test_install_uninstall_font_linux():
+
+    # On Linux, the installed font is a copy of the
+    # requested font, so we need to compare the actual file,
+    # not the file path.
+    dir_path = dirname(realpath(__file__))
+    filename = Path(join(dir_path, "SuperFunky-lgmWw.ttf"))
+
+    fonts_filename = get_system_fonts_filename()
+    assert not any(cmp(filename, f, False) for f in fonts_filename)
+
+    install_font(filename)
+    fonts_filename = get_system_fonts_filename()
+    assert any(cmp(filename, f, False) for f in fonts_filename)
+
+    uninstall_font(filename)
+    fonts_filename = get_system_fonts_filename()
+    assert not any(cmp(filename, f, False) for f in fonts_filename)
+
+@pytest.mark.skipif(not (system() == 'Linux' and hasattr(sys, "getandroidapilevel")), reason="Test runs only on Android")
+def test_install_uninstall_font_android():
+    dir_path = dirname(realpath(__file__))
+    filename = Path(join(dir_path, "SuperFunky-lgmWw.ttf"))
+
+    with pytest.raises(OSNotSupported) as exc_info:
+        install_font(filename)
+    assert str(exc_info.value) == "You cannot install font on android."
+
+    with pytest.raises(OSNotSupported) as exc_info:
+        uninstall_font(filename)
+    assert str(exc_info.value) == "You cannot uninstall font on android."