Skip to content

Commit

Permalink
Allow instantiation with file objects. (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
fcurella authored Aug 21, 2023
1 parent 7005b0b commit ec33e1f
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 0 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ Just import the font class:
from fontbro import Font

font = Font("fonts/MyFont.ttf")

# or you can use any file-like object:
with open("fonts/MyFont.ttf") as fh:
font = Font(fh)
```

### Methods
Expand Down Expand Up @@ -65,6 +69,7 @@ font = Font("fonts/MyFont.ttf")
- [`save`](#save)
- [`save_as_woff`](#save_as_woff)
- [`save_as_woff2`](#save_as_woff2)
- [`save_to_file_object`](#save_to_file_object)
- [`set_name`](#set_name)
- [`set_names`](#set_names)
- [`set_style_flag`](#set_style_flag)
Expand Down Expand Up @@ -548,6 +553,21 @@ Saves font as woff2.
saved_font_path = font.save_as_woff2(filepath=None, overwrite=True)
```

#### `save_to_fileobject`
```python
"""
Writes the font to a file-like object. If no file-object is passed, an
instance of `BytesIO` is created for the user.
:param fileobject: A file-like object to write to.
:returns: The file object that was originally pass, or a new BytesIO
instance.
:rtype: typing.io.IO
"""

fileobject = font.save_to_fileobject(fileobject=None)
```

#### `set_name`
```python
"""
Expand Down
37 changes: 37 additions & 0 deletions fontbro/font.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
import tempfile
from curses import ascii
from io import BytesIO
from pathlib import Path

import fsutil
Expand Down Expand Up @@ -213,11 +214,14 @@ def __init__(self, filepath, **kwargs):
super().__init__()

self._filepath = None
self._fileobject = None
self._kwargs = None
self._ttfont = None

if isinstance(filepath, (Path, str)):
self._init_with_filepath(str(filepath), **kwargs)
elif hasattr(filepath, "read"):
self._init_with_fileobject(filepath, **kwargs)
else:
filepath_type = type(filepath).__name__
raise ValueError(
Expand All @@ -233,6 +237,15 @@ def _init_with_filepath(self, filepath, **kwargs):
except TTLibError as error:
raise ValueError(f"Invalid font at filepath: {filepath!r}.") from error

def _init_with_fileobject(self, fileobject, **kwargs):
try:
self._fileobject = fileobject
self._kwargs = kwargs
self._ttfont = TTFont(self._fileobject, **kwargs)

except TTLibError as error:
raise ValueError(f"Invalid font at fileobject: {fileobject!r}.") from error

def __enter__(self):
return self

Expand Down Expand Up @@ -1001,7 +1014,15 @@ def save(self, filepath=None, overwrite=False):
:raises ValueError: If the filepath is the same of the source font
and overwrite is not allowed.
:raises ValueError: If the font was created from a file object, and filepath is
not specififed.
"""
if not filepath and not self._filepath:
raise ValueError(
"Font doesn't have a filepath. Please specify a filepath to save to."
)

if filepath is None:
filepath = self._filepath

Expand Down Expand Up @@ -1075,6 +1096,22 @@ def save_as_woff2(self, filepath=None, overwrite=True):
flavor=self.FORMAT_WOFF2, filepath=filepath, overwrite=overwrite
)

def save_to_fileobject(self, fileobject=None):
"""
Writes the font to a file-like object. If no file-object is passed, an
instance of `BytesIO` is created for the user.
:param fileobject: A file-like object to write to.
:returns: The file object that was originally pass, or a new BytesIO
instance.
:rtype: typing.io.IO
"""
font = self.get_ttfont()
if fileobject is None:
fileobject = BytesIO()
font.save(fileobject)
return fileobject

def set_name(self, key, value):
"""
Sets the name by its identifier in the font name table.
Expand Down
11 changes: 11 additions & 0 deletions tests/test_init.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from io import BytesIO
from pathlib import Path

from fontbro import Font
Expand All @@ -20,6 +21,16 @@ def test_init_with_filepath_using_pathlib_path(self):
filepath = dirpath / Path("Noto_Sans_TC/NotoSansTC-Regular.otf")
Font(filepath=filepath)

def test_init_with_file_object(self):
filepath = self._get_font_path("/Noto_Sans_TC/NotoSansTC-Regular.otf")
with open(filepath, "rb") as fh:
Font(fh)

def test_init_with_file_object_but_invalid_font_file(self):
empty = BytesIO()
with self.assertRaises(ValueError):
Font(empty)

def test_init_with_filepath_but_invalid_font_file(self):
with self.assertRaises(ValueError):
filepath = self._get_font_path("/Noto_Sans_TC/OFL.txt")
Expand Down
34 changes: 34 additions & 0 deletions tests/test_save.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from io import BytesIO

from fontbro import Font
from tests import AbstractTestCase

Expand All @@ -23,6 +25,38 @@ def test_save_with_font_src_path_as_filepath_without_overwrite(self):
with self.assertRaises(ValueError):
font.save(font_filepath)

def test_save_fileobject(self):
font_filepath = self._get_font_path("/Noto_Sans_TC/NotoSansTC-Regular.otf")
with open(font_filepath, "rb") as fh:
font = Font(fh)
font_saved_path = font.save(font_filepath, overwrite=True)
self.assertEqual(font_saved_path, font_filepath)

def test_save_fileobject_error(self):
filepath = self._get_font_path("/Noto_Sans_TC/NotoSansTC-Regular.otf")
with open(filepath, "rb") as fh:
font = Font(fh)
with self.assertRaises(ValueError):
font.save()

def test_save_to_fileobject(self):
font = self._get_font("/Roboto_Mono/static/RobotoMono-Regular.ttf")
buf = BytesIO()
returned = font.save_to_fileobject(buf)
returned.seek(0)
content = returned.read()
# 00 01 00 00 00 is the file signature for TrueType fonts
self.assertEqual(content[:5], b"\x00\x01\x00\x00\x00")
self.assertIs(buf, returned)

def test_save_to_fileobject_new(self):
font = self._get_font("/Roboto_Mono/static/RobotoMono-Regular.ttf")
returned = font.save_to_fileobject()
returned.seek(0)
content = returned.read()
# 00 01 00 00 00 is the file signature for TrueType fonts
self.assertEqual(content[:5], b"\x00\x01\x00\x00\x00")

def test_save_as_woff(self):
# font = self._get_font('/Noto_Sans_TC/NotoSansTC-Regular.otf')
font = self._get_font("/Roboto_Mono/static/RobotoMono-Regular.ttf")
Expand Down

0 comments on commit ec33e1f

Please sign in to comment.