Repository: python-pillow/Pillow

Issue:
ImageCms.getOpenProfile does not support Unicode path
### What did you do?

I tried to load an ICC file from a non-Latin path.

### What did you expect to happen?

It should load the profile.

### What actually happened?

It throws

```
F:\sync\code\python\_gists>_test.py
Traceback (most recent call last):
  File "C:\Program Files\Python3\lib\site-packages\PIL\ImageCms.py", line 395, in getOpenProfile
    return ImageCmsProfile(profileFilename)
  File "C:\Program Files\Python3\lib\site-packages\PIL\ImageCms.py", line 162, in __init__
    self._set(core.profile_open(profile), profile)
OSError: cannot open profile file

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "F:\sync\code\python\_gists\_test.py", line 3, in <module>
    lsrgb_profile = ImageCms.getOpenProfile('D:\\新\\sRGB.icc')
  File "C:\Program Files\Python3\lib\site-packages\PIL\ImageCms.py", line 397, in getOpenProfile
    raise PyCMSError(v) from v
PIL.ImageCms.PyCMSError: cannot open profile file
```

### What are your OS, Python and Pillow versions?

* OS:  Win7x64
* Python: 3.8.5 x64
* Pillow: 7.2.0

```python
from PIL import Image, ImageCms

lsrgb_profile = ImageCms.getOpenProfile('D:\\新\\sRGB.icc')
```

Similar and fixed issue: https://github.com/python-pillow/Pillow/issues/3145
ImageFont and non latin characters in a path to font
### What did you do?

```
>>> from PIL.ImageFont import truetype
>>> import os.path
>>> fname = 'C:\\temp\\Шрифты\\font.ttf'
>>> os.path.exists(fname)
True
>>> truetype(fname)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\jenyay\.virtualenvs\outwiker-lFR0QK22\lib\site-packages\PIL\ImageFont.py", line 261, in truetype
    return FreeTypeFont(font, size, index, encoding, layout_engine)
  File "C:\Users\jenyay\.virtualenvs\outwiker-lFR0QK22\lib\site-packages\PIL\ImageFont.py", line 144, in __init__
    self.font = core.getfont(font, size, index, encoding, layout_engine=layout_engine)
OSError: cannot open resource
```

If `fname` contains only Latin characters, then everything works.

### What versions of Pillow and Python are you using?

Pillow 5.0.1
Windows 7, Windows 10.
In Linux is OK.


Discussion:
I've created PR #4914 to resolve this.
Can't reproduce on MacOS either.
Info from duplicate https://github.com/python-pillow/Pillow/issues/3480: 

Reproducible with Chinese characters with:

* OS: Windows 10 Pro 1803
* Python: 3.6.5
* Pillow: 5.3.0

But not in CentOS.


Looks like FreeType's `FT_New_Face` doesn't support Unicode paths on Windows.

---

`core.getfont` is `getfont` in `_imagingft.c` (making it hard to debug on Windows).

`getfont` can raise if `error` is set, and they're all return values from `FT_` calls: `FT_New_Face`, `FT_New_Memory_Face`, `FT_Set_Pixel_Sizes`, etc.

https://github.com/python-pillow/Pillow/blob/080bfd3ee1412b401d520fe26c51e2f5515e3a65/src/_imagingft.c#L268-L302

And "cannot open resource" is a FreeType error:
```c
  FT_ERRORDEF_( Cannot_Open_Resource,                        0x01,
                "cannot open resource" )
```
https://github.com/aseprite/freetype2/blob/ae3afbc47184db5c7753318b36237af261c66b09/include/freetype/fterrdef.h#L61-L62

`FT_New_Face` is most likely, as it passes in the `filename`.

---

From https://stackoverflow.com/a/10075289/724176:

> Since freetype is meant to be portable, it only uses fopen, which can only accept const char* filenames. So, either load file to memory (or memory map it) and then use FT_New_Memory_Face to create font or convert wchar_t pszFileName into 8-bit encoding, potentially losing characters due to impossible conversion.
>
> **On linux**, you could attempt to use setlocale so fopen will accept UTF8 strings, convert wchar_t string to UTF8. However **on windows** [it won't work](http://msdn.microsoft.com/en-us/library/x99tb11d%28v=vs.90%29.aspx). So either load file to memory, or convert pszFileName to 8-bit encoding, then pass it to FT_New_Face.

Both of these are similar issues and contain suggestions to load the file and instead pass it to `FT_New_Memory_Face`
* https://freetype.nongnu.narkive.com/XIf73fcO/ft-ft-new-face
* https://github.com/Robmaister/SharpFont/issues/89

---

And Pillow already does something like this. `ImageFont.truetype`'s `font` parameter:

> *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 `fonts/` directory.

If a path is passed in, it uses `FT_New_Face`. If bytes, it uses `FT_New_Memory_Face`.

---

I've not tested this on Windows (and it still works on Mac), please try something like this:

```python
from PIL import ImageFont
from io import BytesIO

FONT_PATH = "方正黄草简体.ttf"

def open_filename(filename):
    font = ImageFont.truetype(filename)
    return font

def open_data(filename):
    with open(filename, "rb") as f:
        font_bytes = BytesIO(f.read())
    font = ImageFont.truetype(font_bytes)
    return font

# print(open_filename(FONT_PATH))
print(open_data(FONT_PATH))
```

Does that help?

One of the threads above suggests not to use `FT_New_Memory_Face` by default, as it loads the whole font into RAM rather than a single font, so we wouldn't want to do that in all cases.

However, perhaps it could be done on Windows when the filename contains Unicode/non-ASCII.

@hugovk , it works on my Windows machine. Thanks~
I've created PR #3785 to resolve this.

Resolving patch:
diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py
--- a/src/PIL/ImageCms.py
+++ b/src/PIL/ImageCms.py
@@ -159,6 +159,14 @@ def __init__(self, profile):
         """
 
         if isinstance(profile, str):
+            if sys.platform == "win32":
+                profile_bytes_path = profile.encode()
+                try:
+                    profile_bytes_path.decode("ascii")
+                except UnicodeDecodeError:
+                    with open(profile, "rb") as f:
+                        self._set(core.profile_frombytes(f.read()))
+                    return
             self._set(core.profile_open(profile), profile)
         elif hasattr(profile, "read"):
             self._set(core.profile_frombytes(profile.read()))


Test patch:
diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py
--- a/Tests/test_imagecms.py
+++ b/Tests/test_imagecms.py
@@ -1,6 +1,7 @@
 import datetime
 import os
 import re
+import shutil
 from io import BytesIO
 
 import pytest
@@ -436,6 +437,19 @@ def truncate_tuple(tuple_or_float):
     assert p.xcolor_space == "RGB "
 
 
+def test_non_ascii_path(tmp_path):
+    skip_missing()
+    tempfile = str(tmp_path / ("temp_" + chr(128) + ".icc"))
+    try:
+        shutil.copy(SRGB, tempfile)
+    except UnicodeEncodeError:
+        pytest.skip("Non-ASCII path could not be created")
+
+    o = ImageCms.getOpenProfile(tempfile)
+    p = o.profile
+    assert p.model == "IEC 61966-2-1 Default RGB Colour Space - sRGB"
+
+
 def test_profile_typesafety():
     """Profile init type safety
 
diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py
--- a/Tests/test_imagefont.py
+++ b/Tests/test_imagefont.py
@@ -111,12 +111,12 @@ def test_font_with_open_file(self):
         with open(FONT_PATH, "rb") as f:
             self._render(f)
 
-    def test_non_unicode_path(self, tmp_path):
+    def test_non_ascii_path(self, tmp_path):
         tempfile = str(tmp_path / ("temp_" + chr(128) + ".ttf"))
         try:
             shutil.copy(FONT_PATH, tempfile)
         except UnicodeEncodeError:
-            pytest.skip("Unicode path could not be created")
+            pytest.skip("Non-ASCII path could not be created")
 
         ImageFont.truetype(tempfile, FONT_SIZE)