Repository: matplotlib/matplotlib
Pull request: Update font_manager to only use registry on Win
* Follow-up on https://github.com/matplotlib/matplotlib/pull/24641, which introduces a bug (fonts are added regardless of their extension).
* Undoes https://github.com/matplotlib/matplotlib/pull/22909 and implements another way to fix https://github.com/matplotlib/matplotlib/issues/22859: don't scrape dirs with `list_fonts` on Windows.

Problem statement:
[Bug]: findSystemFonts should not look in subdirectories of C:\Windows\Fonts\
### Bug summary

findSystemFonts takes the font in the directory: C:\Windows\Fonts\Deleted

It should not take them. They are supposedly deleted. It is better to ignore them.

### Code for reproduction

```python
matplotlib.font_manager.findSystemFonts
```


### Actual outcome

---

### Expected outcome

---

### Additional information

_No response_

### Operating system

Windows

### Matplotlib Version

3.5.1

### Matplotlib Backend

_No response_

### Python version

_No response_

### Jupyter version

_No response_

### Installation

pip

Hints from issue discussion:
Do you have a reference documenting this directory? Thanks. 
No, I don't have any reference. I actually search on the web and I was surprised to find nothing about that.

If you want to access to the folder, write `\\localhost\c$\Windows\Fonts` in your Windows File Explorer.
My point is that if there is no documentation stating that we should ignore this directory, then we shouldn't ignore it. 
I see.
I can only say that when you delete a font, it goes in that folder and want you reboot, this folder seems to become empty.

Someone would need to open ask the question on [Microsoft Q&A](https://docs.microsoft.com/en-us/answers/products/)
Can you try to ask them for a reference? 
I have a suspicion that this is due to not being able to delete an open file on windows but being able to move it.  Thus when you delete a font, they can not be sure no one is using it / detect if someone is using it, move the file to a well-known to be deleted on reboot location, and then on the way up or on the way down (when they are sure no user program is running) they actually delete it.

This is something we should try to nerd-snipe Raymond Chen (https://devblogs.microsoft.com/oldnewthing/ /@oldnewthing) into explaining for real ;).
> Can you try to ask them for a reference?

Since @tacaswell want to contact Raymond Chen, do you still want me to open a thread on [Microsoft Q&A](https://docs.microsoft.com/en-us/answers/products/) ?
That was (mostly) a joke, but yes reaching out through Microsoft's support channels to get a definitive answer to this would be greatly appreciated!
Here you go: https://docs.microsoft.com/en-us/answers/questions/823422/what-is-exactly-cwindowsfontsdeleted.html?childToView=823435#answer-823435
The suggested solution of using EnumFontFamiliesEx doesn't really work for us, as we need an actual filename, not just a win32 LOGFONT structure (as we rely on FreeType for rendering, not on the Windows API).  A quick googling suggests that 1) many people want to go from LOGFONT to filename and 2) they are always told that this is not possible.
The best I found was https://stackoverflow.com/a/11623081/378080 which uses DirectWrite and would probably require quite a bit of engineering to set up.

---

Perhaps a much simpler point is to ask whether Windows actually searches for font files in *subdirectories* of c:/windows/fonts (this is not clear to me from the docs, and could easily be tested if you have a windows machine available, e.g. by temporarily moving a font to a subdirectory and check (perhaps after reboot) if Windows detects it.  If it doesn't, then we just need to change the search algorithm to ignore subdirectories :-)

Commit message: DOC: add behavior change note for only considering fonts in registry

Patch to review:
diff --git a/doc/api/next_api_changes/behavior/24655-AK.rst b/doc/api/next_api_changes/behavior/24655-AK.rst
new file mode 100644
index 000000000000..840c0e50fb64
--- /dev/null
+++ b/doc/api/next_api_changes/behavior/24655-AK.rst
@@ -0,0 +1,14 @@
+On windows only fonts known to the registry will be discovered
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Previously, Matplotlib would recursively walk user and system font directories
+to discover fonts, however this lead to a number of undesirable behaviors
+including finding deleted fonts.  Now Matplotlib will only find fonts that are
+known to the Windows.
+
+This means that any user installed fonts must got through the Windows font
+installer rather than simply being copied to the correct folder.
+
+This only impacts the set of fonts Matplotlib will consider when using
+`matplotlib.font_manager.find_font`, directly pass the path to a font to use an
+arbitrary font (see :doc:`/gallery/text_labels_and_annotations/fonts_file`).
diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py
index c775a09136d6..8a4b52e96f32 100644
--- a/lib/matplotlib/font_manager.py
+++ b/lib/matplotlib/font_manager.py
@@ -186,16 +186,11 @@ def list_fonts(directory, extensions):
     recursively under the directory.
     """
     extensions = ["." + ext for ext in extensions]
-    if sys.platform == 'win32' and directory == win32FontDirectory():
-        return [os.path.join(directory, filename)
-                for filename in os.listdir(directory)
-                if os.path.isfile(os.path.join(directory, filename))]
-    else:
-        return [os.path.join(dirpath, filename)
-                # os.walk ignores access errors, unlike Path.glob.
-                for dirpath, _, filenames in os.walk(directory)
-                for filename in filenames
-                if Path(filename).suffix.lower() in extensions]
+    return [os.path.join(dirpath, filename)
+            # os.walk ignores access errors, unlike Path.glob.
+            for dirpath, _, filenames in os.walk(directory)
+            for filename in filenames
+            if Path(filename).suffix.lower() in extensions]
 
 
 def win32FontDirectory():
@@ -275,7 +270,7 @@ def findSystemFonts(fontpaths=None, fontext='ttf'):
     if fontpaths is None:
         if sys.platform == 'win32':
             installed_fonts = _get_win32_installed_fonts()
-            fontpaths = MSUserFontDirectories + [win32FontDirectory()]
+            fontpaths = []
         else:
             installed_fonts = _get_fontconfig_fonts()
             if sys.platform == 'darwin':
diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py
index 6fd1097ce196..3724db1e1b43 100644
--- a/lib/matplotlib/tests/test_font_manager.py
+++ b/lib/matplotlib/tests/test_font_manager.py
@@ -200,7 +200,7 @@ def test_user_fonts_win32():
         pytest.xfail("This test should only run on CI (appveyor or azure) "
                      "as the developer's font directory should remain "
                      "unchanged.")
-
+    pytest.xfail("We need to update the registry for this test to work")
     font_test_file = 'mpltest.ttf'
 
     # Precondition: the test font should not be available


Code review comments:
diff_hunk: @@ -0,0 +1,14 @@
+On windows only fonts known to the registry will be discovered
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Previously, Matplotlib would recursively walk user and system font directories
+to discover fonts, however this lead to a number of undesirable behaviors
+including finding deleted fonts.  Now Matplotlib will only find fonts that are
+known to the Windows.
+
+This means that any user installed fonts must got through the Windows font
path: doc/api/next_api_changes/behavior/24655-AK.rst
text: @user1:
```suggestion
This means that any user installed fonts must go through the Windows font
```

diff_hunk: @@ -0,0 +1,14 @@
+On windows only fonts known to the registry will be discovered
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Previously, Matplotlib would recursively walk user and system font directories
+to discover fonts, however this lead to a number of undesirable behaviors
+including finding deleted fonts.  Now Matplotlib will only find fonts that are
+known to the Windows.
+
+This means that any user installed fonts must got through the Windows font
+installer rather than simply being copied to the correct folder.
+
+This only impacts the set of fonts Matplotlib will consider when using
+`matplotlib.font_manager.find_font`, directly pass the path to a font to use an
+arbitrary font (see :doc:`/gallery/text_labels_and_annotations/fonts_file`).
path: doc/api/next_api_changes/behavior/24655-AK.rst
text: @user1:
```suggestion
`matplotlib.font_manager.findfont`, directly pass the path to a font to use an
arbitrary font (see :doc:`/gallery/text_labels_and_annotations/font_file`).
```

diff_hunk: @@ -0,0 +1,14 @@
+On windows only fonts known to the registry will be discovered
path: doc/api/next_api_changes/behavior/24655-AK.rst
text: @user1:
```suggestion
On Windows only fonts known to the registry will be discovered
```