pyfonts 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyfonts/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .main import load_font
2
+ from .preview_font import preview_font
3
+
4
+ __all__ = ["load_font", "preview_font"]
pyfonts/get_font.py ADDED
@@ -0,0 +1,76 @@
1
+ from urllib.request import urlopen
2
+ from urllib.error import URLError, HTTPError
3
+ from tempfile import NamedTemporaryFile
4
+ from matplotlib.font_manager import FontProperties
5
+
6
+ from .is_valid import _is_url, _is_valid_raw_url
7
+
8
+
9
+ def _get_font_from_url(font_location: str) -> FontProperties:
10
+ """
11
+ Retrieves a font from a Github url. The function attempts to access the font
12
+ at the given url by checking a few elements, creates a temporary file with the
13
+ font found and returns a FontProperties.
14
+
15
+ Params:
16
+ - font_location: url that points to the binary font file on Github
17
+
18
+ Returns:
19
+ - matplotlib.font_manager.FontProperties: A FontProperties object containing the loaded font.
20
+ """
21
+
22
+ if not _is_url(font_location):
23
+ raise ValueError(f"`font_location` must be an url, not: {font_location}.")
24
+
25
+ elif not _is_valid_raw_url(font_location):
26
+ raise ValueError(
27
+ f"""
28
+ The URL provided ({font_location}) does not appear to be valid.
29
+ It must point to a binary font file from Github.
30
+ Have you forgotten to append `?raw=true` to the end of the URL?
31
+ """
32
+ )
33
+
34
+ else:
35
+ try:
36
+ with NamedTemporaryFile(delete=False) as temp_file:
37
+ response = urlopen(font_location)
38
+ temp_file.write(response.read())
39
+
40
+ except HTTPError as e:
41
+ if e.code == 404:
42
+ raise Exception(
43
+ "404 error. The url passed does not exist: font file not found."
44
+ )
45
+
46
+ except URLError:
47
+ raise Exception(
48
+ "Failed to load font. This may be due to a lack of internet connection."
49
+ )
50
+
51
+ font = FontProperties(fname=temp_file.name)
52
+ return font
53
+
54
+
55
+ def _get_local_font(font_location: str) -> FontProperties:
56
+ """
57
+ Retrieves a font from a local path.
58
+
59
+ Params:
60
+ - font_location: path to a font file.
61
+
62
+ Returns:
63
+ - matplotlib.font_manager.FontProperties: A FontProperties object containing the loaded font.
64
+ """
65
+
66
+ if _is_url(font_location):
67
+ raise ValueError("`font_location` must point to a local file on your computer.")
68
+
69
+ else:
70
+ font = FontProperties(fname=font_location)
71
+ try:
72
+ font.get_name()
73
+ except FileNotFoundError:
74
+ raise ValueError(f"Font file not found at : '{font_location}'")
75
+
76
+ return font
pyfonts/is_valid.py ADDED
@@ -0,0 +1,43 @@
1
+ import re
2
+ from urllib.parse import urlparse
3
+
4
+
5
+ def _is_url(s: str) -> bool:
6
+ """
7
+ Tests whether a string is an url.
8
+
9
+ Parameters:
10
+ - s: a string.
11
+
12
+ Returns:
13
+ - a boolean indicating whether the string is an url or not.
14
+ """
15
+ is_an_url = urlparse(s).scheme != ""
16
+ return is_an_url
17
+
18
+
19
+ def _is_valid_raw_url(url: str) -> bool:
20
+ """
21
+ Tests whether an url from Github pointing to a font
22
+ is actually a raw url (e.g pointing to the binary font file and not the Github view).
23
+
24
+ There are 3 ways/patterns to point to the raw version of a file from Github that are defined
25
+ in `patterns`.
26
+
27
+ Parameters:
28
+ - url: the url of the font file.
29
+
30
+ Returns:
31
+ - a boolean indicating whether the url corresponds to a raw file.
32
+ """
33
+ patterns = [
34
+ r"^https://github\.com/[^/]+/[^/]+/blob/[^/]+/.+\.(ttf|otf|woff|woff2)\?raw=true$",
35
+ r"^https://github\.com/[^/]+/[^/]+/raw/[^/]+/.+\.(ttf|otf|woff|woff2)$",
36
+ r"^https://raw\.githubusercontent\.com/[^/]+/[^/]+/[^/]+/.+\.(ttf|otf|woff|woff2)$",
37
+ ]
38
+
39
+ for pattern in patterns:
40
+ if re.match(pattern, url):
41
+ return True
42
+
43
+ return False
pyfonts/main.py ADDED
@@ -0,0 +1,33 @@
1
+ from matplotlib.font_manager import FontProperties
2
+ from typing import Optional
3
+
4
+ from .get_font import _get_font_from_url, _get_local_font
5
+
6
+
7
+ def load_font(
8
+ font_url: Optional[str] = None,
9
+ font_path: Optional[str] = None,
10
+ ) -> FontProperties:
11
+ """
12
+ Loads a FontProperties object from a remote Github repo or a local file.
13
+
14
+ Parameters:
15
+ - font_url: A URL pointing to a binary font file from Github.
16
+ - font_path: The local file path of the font.
17
+
18
+ Returns:
19
+ - matplotlib.font_manager.FontProperties: A FontProperties object containing the loaded font.
20
+ """
21
+ if font_url and font_path:
22
+ raise ValueError(
23
+ "You must provide only one of the following: `font_url` or `font_path`."
24
+ )
25
+ elif font_url:
26
+ font = _get_font_from_url(font_url)
27
+ elif font_path:
28
+ font = _get_local_font(font_path)
29
+ else:
30
+ raise ValueError(
31
+ "You must provide one of the following: `font_url` or `font_path`."
32
+ )
33
+ return font
@@ -0,0 +1,45 @@
1
+ from .main import load_font
2
+ import matplotlib.pyplot as plt
3
+ from typing import Optional
4
+
5
+
6
+ def preview_font(
7
+ font_url: Optional[str] = None,
8
+ font_path: Optional[str] = None,
9
+ ):
10
+ """
11
+ Preview a font.
12
+ """
13
+ font = load_font(font_url, font_path)
14
+
15
+ plt.figure(figsize=(10, 5))
16
+ plt.text(
17
+ 0.5,
18
+ 0.5,
19
+ "Hello, World From PyFonts!",
20
+ fontsize=30,
21
+ ha="center",
22
+ va="center",
23
+ font=font,
24
+ )
25
+ plt.text(
26
+ 0.5,
27
+ 0.35,
28
+ "This is a test.",
29
+ fontsize=25,
30
+ ha="center",
31
+ va="center",
32
+ font=font,
33
+ )
34
+ plt.text(
35
+ 0.5,
36
+ 0.65,
37
+ "How about this?",
38
+ fontsize=20,
39
+ ha="center",
40
+ va="center",
41
+ font=font,
42
+ )
43
+
44
+ plt.axis("off")
45
+ plt.show()
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Joseph Barbier
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.1
2
+ Name: pyfonts
3
+ Version: 0.0.1
4
+ Summary: A simple way to load fonts for matplotlib
5
+ Home-page: https://github.com/JosephBARBIERDARNAL/pyfonts/blob/main/README.md
6
+ Author: Joseph Barbier
7
+ Author-email: Joseph Barbier <joseph.barbierdarnal@gmail.com>
8
+ Project-URL: Homepage, https://github.com/JosephBARBIERDARNAL/pyfonts
9
+ Project-URL: Issues, https://github.com/JosephBARBIERDARNAL/pyfonts/issues
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Framework :: Matplotlib
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: matplotlib
18
+
19
+ # PyFonts
20
+
21
+ <img src="https://github.com/JosephBARBIERDARNAL/static/blob/main/python-libs/pyfonts/image.png?raw=true" alt="pyfonts logo" align="right" width="200px"/>
22
+
23
+ A simple (and reproducible) way to load fonts for `matplotlib`.
24
+
25
+ Check out [the online documentation](https://python-graph-gallery.com/pyfonts/).
26
+
27
+ <br><br>
28
+
29
+ # Installation
30
+
31
+ _Note: pyfonts requires **Python 3.9** and above._
32
+
33
+ You can install `pyfonts` directly from PyPI with:
34
+
35
+ ```
36
+ pip install pyfonts
37
+ ```
38
+
39
+ <br><br>
40
+
41
+ # Quick start
42
+
43
+ `pyfonts` is designed to make the code of your graphs more reproducible by removing the need to add the local path to the font files. For example:
44
+
45
+ ```python
46
+ from pyfonts import load_font
47
+ import matplotlib.pyplot as plt
48
+
49
+ font = load_font(
50
+ font_url="https://github.com/google/fonts/raw/main/apache/ultra/Ultra-Regular.ttf"
51
+ )
52
+
53
+ fig, ax = plt.subplots(figsize=(10, 6))
54
+ ax.text(
55
+ x=0.5,
56
+ y=0.5,
57
+ s=f"What an easy way to load fonts, isn't it?",
58
+ font=font,
59
+ fontsize=20,
60
+ ha="center",
61
+ )
62
+ plt.show()
63
+ ```
64
+
65
+ ![output of quick start](https://github.com/JosephBARBIERDARNAL/pyfonts/blob/main/img/quickstart.png?raw=true)
66
+
67
+ <br><br>
68
+
69
+ # Usage guide
70
+
71
+ Check out [the online documentation](https://python-graph-gallery.com/pyfonts/).
72
+
73
+ <br><br>
74
+
75
+ # Contributions
76
+
77
+ Contributions and feedback are welcome.
78
+
79
+ There's not much that needs to be implemented at the moment. If you've found a bug or want to request a new feature, open an [issue](https://github.com/JosephBARBIERDARNAL/pyfonts/issues).
80
+
81
+ <br><br><br>
@@ -0,0 +1,14 @@
1
+ pyfonts/__init__.py,sha256=9_bJ1B6DvZGfQwzS70QiaFReRzUL_nJcFmb2D0wKxD8,108
2
+ pyfonts/get_font.py,sha256=THQu4PR3jMGaItT584i3Fem2P6STvRQk3gzUn8z4lio,2406
3
+ pyfonts/is_valid.py,sha256=sKezwlLjcZGR9ViPfwMAFb5MTXyi9rhULIlqo-29zes,1173
4
+ pyfonts/main.py,sha256=lc97eBRVJxYExI52jHPBgokWfLkdKarH5e4di-7e81Y,1006
5
+ pyfonts/preview_font.py,sha256=mwcQ0E0h1GasxoyB0XCqlvBu14Uox2OOHekMKrxmPk4,809
6
+ tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ tests/test_is_valid.py,sha256=v-2Bbg5otcd3P7lTkc0cbxHFZZRan_S-aEbjOsYHNw0,2224
8
+ tests/test_load_font.py,sha256=UHdhiN5O1mXRPfegE1tQ4Q7eVi3_sO0QHCXFhZ4ap3Q,729
9
+ tests/test_preview_font.py,sha256=JvpOjX4WO6rHKhtGdn_hd7vJ6KUwzfquvz6uTFQXH-I,673
10
+ pyfonts-0.0.1.dist-info/LICENSE,sha256=KvIhR13NJbyEXp5UJS9cu5feh8Alen7aQv_zvT-Hvcg,1071
11
+ pyfonts-0.0.1.dist-info/METADATA,sha256=1KEAwYkKLuarUFlvNy7Lx_d6BbAq_eeFmnITJziJfaw,2211
12
+ pyfonts-0.0.1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
13
+ pyfonts-0.0.1.dist-info/top_level.txt,sha256=GlEnd_EJc2WXFW7B_w1xI_eYQkN4avCttKYTd9EAXZw,14
14
+ pyfonts-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ pyfonts
2
+ tests
tests/__init__.py ADDED
File without changes
tests/test_is_valid.py ADDED
@@ -0,0 +1,67 @@
1
+ import pytest
2
+ from pyfonts.is_valid import _is_valid_raw_url, _is_url
3
+
4
+
5
+ @pytest.mark.parametrize(
6
+ "input_string, expected_result",
7
+ [
8
+ ("https://www.example.com", True),
9
+ ("http://example.com", True),
10
+ ("ftp://ftp.example.com", True),
11
+ ("www.example.com", False),
12
+ ("example.com", False),
13
+ ("just a string", False),
14
+ ("", False),
15
+ ("file:///C:/Users/username/Documents/file.txt", True),
16
+ ("mailto:user@example.com", True),
17
+ ],
18
+ )
19
+ def test_is_url(input_string, expected_result):
20
+ assert _is_url(input_string) == expected_result
21
+
22
+
23
+ def test_is_url_type_error():
24
+ with pytest.raises(AttributeError):
25
+ _is_url(123)
26
+
27
+
28
+ @pytest.mark.parametrize(
29
+ "url,expected",
30
+ [
31
+ ("https://github.com/user/repo/blob/master/font.ttf?raw=true", True),
32
+ ("https://github.com/user/repo/raw/master/font.otf", True),
33
+ ("https://raw.githubusercontent.com/user/repo/master/font.woff", True),
34
+ (
35
+ "https://raw.githubusercontent.com/user/repo/branch-name/subfolder/font.woff2",
36
+ True,
37
+ ),
38
+ ("https://github.com/user/repo/blob/master/font.ttf", False),
39
+ ("https://github.com/user/repo/raw/master/font.txt", False),
40
+ ("https://raw.githubusercontent.com/user/repo/master/font.exe", False),
41
+ ("https://example.com/font.ttf", False),
42
+ ("https://github.com/user/repo/tree/master/fonts/font.ttf", False),
43
+ ("https://github.com/user/repo/blob/master/font.ttf?raw=false", False),
44
+ (
45
+ "https://github.com/user/repo/blob/master/font.ttf?raw=true&param=value",
46
+ False,
47
+ ),
48
+ ("https://github.com/user/repo/raw/master/font.WOFF", False),
49
+ ("https://raw.githubusercontent.com/user/repo/master/font.ttf?raw=true", False),
50
+ ],
51
+ )
52
+ def test_is_valid_raw_url(url, expected):
53
+ assert _is_valid_raw_url(url) == expected
54
+
55
+
56
+ def test_is_valid_raw_url_with_empty_string():
57
+ assert _is_valid_raw_url("") == False
58
+
59
+
60
+ def test_is_valid_raw_url_with_none():
61
+ with pytest.raises(TypeError):
62
+ _is_valid_raw_url(None)
63
+
64
+
65
+ def test_is_valid_raw_url_with_non_string():
66
+ with pytest.raises(TypeError):
67
+ _is_valid_raw_url(123)
@@ -0,0 +1,26 @@
1
+ import pytest
2
+ from unittest.mock import patch
3
+ from matplotlib.font_manager import FontProperties
4
+ from pyfonts import load_font
5
+
6
+
7
+ def test_load_font_with_url():
8
+ font = load_font(
9
+ font_url="https://github.com/JosephBARBIERDARNAL/pyfonts/blob/main/tests/Ultra-Regular.ttf?raw=true"
10
+ )
11
+ assert isinstance(font, FontProperties)
12
+
13
+
14
+ def test_load_font_with_path():
15
+ font = load_font(font_path="tests/Ultra-Regular.ttf")
16
+ assert isinstance(font, FontProperties)
17
+
18
+
19
+ def test_load_font_invalid_input():
20
+ with pytest.raises(ValueError):
21
+ load_font(font_url="http://example.com/font.ttf", font_path="/path/to/font.ttf")
22
+
23
+
24
+ def test_load_font_no_input():
25
+ with pytest.raises(ValueError):
26
+ load_font()
@@ -0,0 +1,21 @@
1
+ import pytest
2
+ from unittest.mock import patch, MagicMock
3
+ from matplotlib.font_manager import FontProperties
4
+ from pyfonts import preview_font, load_font
5
+
6
+
7
+ @pytest.fixture
8
+ def mock_load_font():
9
+ with patch("pyfonts.load_font") as mock:
10
+ mock.return_value = FontProperties()
11
+ yield mock
12
+
13
+
14
+ def test_preview_font_raises_value_error_on_invalid_input(mock_load_font):
15
+ mock_load_font.side_effect = ValueError(
16
+ "You must provide only one of the following: `font_name`, `font_url` or `font_path`."
17
+ )
18
+ with pytest.raises(ValueError):
19
+ preview_font(
20
+ font_url="http://example.com/font.ttf", font_path="/path/to/font.ttf"
21
+ )