Repository: python-pillow/Pillow
Pull request: Note a Windows limit on opening fonts
Resolves #3730

The issue reports `OSError: cannot open resource` when opening many fonts on Windows. This is a FreeType error that is thrown, presumably because on Windows, ['The C run-time libraries have a 512 limit for the number of files that can be open at any one time.'](https://docs.microsoft.com/en-us/cpp/c-runtime-library/file-handling?view=vs-2017)

This PR suggests resolving this situation by simply documenting the limitation.

Also improved some other FreeType documentation while I'm here.

Problem statement:
"OSError: cannot open resource" when trying to load more than exactly 509 ImageFonts
### What did you do?
For a data generator, I need to use lots of fonts in different sizes. Randomly one of that is used to generate a sample. Since I didn't want to load a font every time we generate a sample, I created a nested dictionary that dynamicalle loads fonts of a given size when it wasn't loaded previously. It worked fine at the beginning. Then I tried to generate some more samples (>1k) and got "OSError: cannot open resource" every time. I double checked the paths and tried to load the font with the same path and that worked just fine. It definitely isn't related to the path encoding bugs.

To reproduce that, you can use the code below.


### What did you expect to happen?
It should be able to load fonts until the RAM is full ;)

### What actually happened?
OSError after loading exactly 509 fonts.
After clearing my dictionary as soon as the error occurs (temporary workaround), it works fine again.

### What are your OS, Python and Pillow versions?

* OS: Win10
* Python: 3.6
* Pillow: 5.4.1

Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.

The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as plone, Django, or buildout, try to replicate the issue just using Pillow. 

```python
from PIL import ImageFont

test = []

for i in range(1000):
        test.append(ImageFont.truetype("fonts/AbyssinicaSIL-R.ttf", 15))
```

Hints from issue discussion:
When I run your script with a different font on my macOS machine, I have no problems. However, when I run the following code -

```python
from PIL import ImageFont

test = []

for i in range(1000):
	print(i)
	test.append(open("fonts/AbyssinicaSIL-R.ttf", "r"))
```

It stops at 253 with `OSError: [Errno 24] Too many open files`. Could you run this code? I suspect that it will stop at 509, revealing that you are experiencing a problem with too many open files.
Nope, no problem there. The "too many open files" error occurs only if I try to get beyond 8187 - that's far from the 509 here. :/
After a bit of experimenting, the code below seems to work fine as workaround:
```python
from PIL import ImageFont
from io import BytesIO

test = []

for i in range(1000):
    file = open("fonts/AbyssinicaSIL-R.ttf", "rb")
    bytes_font = BytesIO(file.read())
    test.append(ImageFont.truetype(bytes_font, 15))
```
Does this happen for just this font, or for multiple different fonts, including standard ones like Helvetica?
https://docs.microsoft.com/en-us/cpp/c-runtime-library/file-handling?view=vs-2017

> The C run-time libraries have a 512 limit for the number of files that can be open at any one time.

I presume that is the limit that you are hitting. So I wouldn't think of this as a bug in Pillow. Any thoughts?
I randomly choose from a range of different fonts in a directory in my original code, so this happens for other fonts, too.

Since the limit is at 512 and this error occurs after exactly 509, I'm not sure if this is connected to each other. Even if it was, a more meaningful exception should be raised in that case.

Is there actually any need for the file to be kept open in the current implementation of Pillow when calling ImageFont.truetype?
I don't know what a more meaningful exception could be. If you have ideas, feel free to put them forward.

Pillow is not handling the fonts here by itself, it is making use of FreeType. As an aside to anyone else reading this, now that [Pillow has FreeType support in AppVeyor](https://github.com/python-pillow/Pillow/pull/3774), I was able to replicate the error there.

From my understanding, it is FreeType that is actually keeping the file pointer open, and the 'Cannot open resource' message originates from FreeType. We call `FT_Done_Face` when we are done with the font, but that's not applicable here, since the fonts in your array might still be used.

So I don't see any reasonable action to take to improve this. We could stop using FreeType, but that's extreme. We could load the load into memory, like you're doing with `BytesIO`, but there's no advantage over that being run externally by you. Feel free to add any more information or disagree with my thoughts.
If you can track down the "real" error before it's thrown maybe just a reference to that issue could be enough.
Or we just hope that if somebody has the same issue he will stumble upon this himself, but i'd still prefer an more meaningful error message if it's not too complicated.

To stop using FreeType because of this would indeed be decent overkill ;)
Hi, i experienced the same exact issue where opening exactly 509 fonts will raise an OSError.

I'm using Win 10, Python 3.6, Pillow 5.4.1

I'm also working on a project that uses up to 60,000 different fonts.

@Luux did you manage to get it resolved?
@delzac the workaround is to [read the font into memory first](https://github.com/python-pillow/Pillow/issues/3730#issuecomment-474802149)

Commit message: Noted a Windows limit on opening fonts [ci skip]

Patch to review:
diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py
index e2e6af33254..8279503be28 100644
--- a/src/PIL/ImageFont.py
+++ b/src/PIL/ImageFont.py
@@ -546,11 +546,20 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
     This function loads a font object from the given file or file-like
     object, and creates a font object for a font of the given size.
 
+    Note that Pillow uses FreeType to open font files. If you are opening many
+    fonts simultaneously on Windows, be aware that Windows limits the number of
+    files that can be open in C at once to 512. If you approach that limit, an
+    ``OSError`` may be thrown, reporting that FreeType "cannot open resource".
+
     This function requires the _imagingft service.
 
     :param font: A filename or file-like object containing a TrueType font.
-                     Under Windows, if the file is not found in this filename,
-                     the loader also looks in Windows :file:`fonts/` directory.
+                 If the file is not found in this filename, the loader may also
+                 search in other directories, such as the :file:`fonts/`
+                 directory on Windows or :file:`/Library/Fonts/`,
+                 :file:`/System/Library/Fonts/` and :file:`~/Library/Fonts/` on
+                 macOS.
+
     :param size: The requested size, in points.
     :param index: Which font face to load (default is first available face).
     :param encoding: Which font encoding to use (default is Unicode). Common


Code review comments:
diff_hunk: @@ -546,11 +546,20 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
     This function loads a font object from the given file or file-like
     object, and creates a font object for a font of the given size.
 
+    Note that Pillow uses FreeType to open font files. If you are opening many
path: src/PIL/ImageFont.py
text: @user1:
No need for "Note that":

```suggestion
   Pillow uses FreeType to open font files. If you are opening many
```