Programmatically generate SVG (vector) images, animations, and interactive Jupyter widgets

Add google font embed

Changed files
+95 -1
drawsvg
examples
+17
README.md
···
Note: The above example currently only works in `jupyter notebook`, not `jupyter lab`.
+
### Embed custom fonts
+
```python
+
import drawsvg as draw
+
+
d = draw.Drawing(400, 100, origin='center')
+
d.embed_google_font('Permanent Marker', text=set('Text with custom font'))
+
+
d.append(draw.Text('Text with custom font', 35, 0, 0, center=True,
+
font_family='Permanent Marker', font_style='italic'))
+
+
d.save_svg('font.svg')
+
d # Custom fonts work in most browsers but not in rasterize(), save_png(), or save_video()
+
```
+
+
[![Example output image](https://raw.githubusercontent.com/cduck/drawsvg/master/examples/font.svg?sanitize=true)](https://github.com/cduck/drawsvg/blob/master/examples/font.svg)
+
+
---
# Full-feature install
+15 -1
drawsvg/drawing.py
···
import xml.sax.saxutils as xml
from . import (
-
types, elements as elements_module, raster, video, jupyter, native_animation
+
types, elements as elements_module, raster, video, jupyter,
+
native_animation, font_embed,
)
···
self.append(elements_module.Title(text, **kwargs))
def append_css(self, css_text):
self.css_list.append(css_text)
+
def embed_google_font(self, family, text=None, display='swap', **kwargs):
+
'''Download SVG-embeddable CSS from Google fonts.
+
+
Args:
+
family: Name of font family or list of font families.
+
text: The set of characters required from the font. Only a font
+
subset with these characters will be downloaded.
+
display: The font-display CSS value.
+
**kwargs: Other URL parameters sent to
+
https://fonts.googleapis.com/css?...
+
'''
+
self.append_css(font_embed.download_google_font_css(
+
family, text=text, display=display, **kwargs))
def append_javascript(self, js_text, onload=None):
if onload:
if self.svg_args.get('onload'):
+48
drawsvg/font_embed.py
···
+
import urllib.request, urllib.parse
+
import re
+
+
from . import url_encode
+
+
+
def download_url(url):
+
with urllib.request.urlopen(url) as r:
+
return r.read()
+
+
def download_url_to_data_uri(url, mime='application/octet-stream'):
+
data = download_url(url)
+
return url_encode.bytes_as_data_uri(data, strip_chars='', mime=mime)
+
+
def embed_css_resources(css):
+
'''Replace all URLs in the CSS string with downloaded data URIs.'''
+
regex = re.compile(r'url\((https?://[^)]*)\)')
+
def repl(match):
+
url = match[1]
+
uri = download_url_to_data_uri(url)
+
return f'url({uri})'
+
embedded, _ = regex.subn(repl, css)
+
return embedded
+
+
def download_google_font_css(family, text=None, display='swap', **kwargs):
+
'''Download SVG-embeddable CSS from Google fonts.
+
+
Args:
+
family: Name of font family or list of font families.
+
text: The set of characters required from the font. Only a font subset
+
with these characters will be downloaded.
+
display: The font-display CSS value.
+
**kwargs: Other URL parameters sent to
+
https://fonts.googleapis.com/css?...
+
'''
+
if not isinstance(family, str):
+
family = '|'.join(family) # Request a list of families
+
args = dict(family=family, display=display)
+
if text is not None:
+
if not isinstance(text, str):
+
text = ''.join(text)
+
args['text'] = text
+
args.update(kwargs)
+
params = urllib.parse.urlencode(args)
+
url = f'https://fonts.googleapis.com/css?{params}'
+
with urllib.request.urlopen(url) as r:
+
css = r.read().decode('utf-8')
+
return embed_css_resources(css)
+15
examples/font.svg
···
+
<?xml version="1.0" encoding="UTF-8"?>
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
+
width="400" height="100" viewBox="-200.0 -50.0 400 100">
+
<style>/*<![CDATA[*/@font-face {
+
font-family: 'Permanent Marker';
+
font-style: normal;
+
font-weight: 400;
+
font-display: swap;
+
src: url(data:application/octet-stream;base64,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) format('truetype');
+
}
+
/*]]>*/</style>
+
<defs>
+
</defs>
+
<text x="0" y="0" font-size="35" font-family="Permanent Marker" font-style="italic" text-anchor="middle" dominant-baseline="central">Text with custom font</text>
+
</svg>