1

Is there a way to load a font with Pillow library directly from url, preferably into Google Colab? I tried something like

from PIL import Image, ImageDraw, ImageFont ImageFont.truetype("https://github.com/googlefonts/roboto/blob/master/src/hinted/Roboto-Regular.ttf?raw=true", 15)

Yet I get an error of OSError: cannot open resource. I tried with Google Fonts too, but to no avail. enter image description here

delimiter
  • 745
  • 4
  • 13

2 Answers2

4

You can
(1) Fetch the font with HTTP GET request, using urllib.request.urlopen()
(2) memoize the result with @functools.lrucache or @memoization.cache so the font is not fetched every time you run the function and
(3) Pass the contents as a file-like object with io.BytesIO

from PIL import ImageFont
import urllib.request
import functools
import io


@functools.lru_cache
def get_font_from_url(font_url):
    return urllib.request.urlopen(font_url).read()


def webfont(font_url):
    return io.BytesIO(get_font_from_url(font_url))


if __name__ == "__main__":
    font_url = "https://github.com/googlefonts/roboto/blob/master/src/hinted/Roboto-Regular.ttf?raw=true"
    with webfont(font_url) as f:
        imgfnt = ImageFont.truetype(f, 15)

There is also python-memoization (pip install memoization) for alternative way of memoizing. Usage would be

from memoization import cache 

@cache
def get_font_from_url(font_url):
    return urllib.request.urlopen(font_url).read()

memoization speed

Without memoization:

In [1]: timeit get_font_from_url(font_url)
The slowest run took 4.95 times longer than the fastest. This could mean that an intermediate result is being cached.
1.32 s ± 1.11 s per loop (mean ± std. dev. of 7 runs, 1 loop each)

With memoization:

In [1]: timeit get_font_from_url(font_url)
The slowest run took 11.00 times longer than the fastest. This could mean that an intermediate result is being cached.
271 ns ± 341 ns per loop (mean ± std. dev. of 7 runs, 1 loop each)
```t
Niko Föhr
  • 28,336
  • 10
  • 93
  • 96
  • That's good and thorough, thank you for this. @Mark 's response is still a quick short solution, so I'll accept his answer, hope you don't mind. – delimiter Nov 06 '20 at 16:16
  • 1
    Thanks :) Yeah I would actually not written an answer if I had seen there is already one. You could add to this some retrying (for example using tenacity) and also zip-file handling. Zip-file handling could be done just by adding a parameter: `webfont(font_url, filetype='zip')` and then adding unzip if asked for. – Niko Föhr Nov 06 '20 at 16:22
1

Try it like this:

import requests
from io import BytesIO

req = requests.get("https://github.com/googlefonts/roboto/blob/master/src/hinted/Roboto-Regular.ttf?raw=true")

font = ImageFont.truetype(BytesIO(req.content), 72)
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • That's a quick one. Worth mentioning that BytesIO lives in io library, we'd need to import that one too. What if the font is in a zip file, do you have a quick hint for that? Or is it better to post another question? – delimiter Nov 06 '20 at 15:51
  • 2
    Questions are free (and answers) so go ahead and ask another... being careful to provide a representative URL – Mark Setchell Nov 06 '20 at 15:58
  • Added another question here, maybe you can take a look? https://stackoverflow.com/questions/64730209/accessing-extracted-zip-file-in-colab – delimiter Nov 07 '20 at 19:42