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

Compare changes

Choose any two refs to compare.

+1 -1
.gitignore
···
/MANIFEST
/dist
/*.egg-info
-
+
*.ipynb_checkpoints
+31 -4
README.md
···
An interactive [Jupyter notebook](https://jupyter.org) widget, `drawsvg.widgets.DrawingWidget`, [is included](#interactive-widget) that can update drawings based on mouse events. The widget does not yet work in Jupyter lab.
+
[SVG quick reference docs](https://cduck.github.io/drawsvg/)
+
# Install
···
#d.display_image() # Display SVG as an image (will not be interactive)
#d.display_iframe() # Display as interactive SVG (alternative)
#d.as_gif('orbit.gif', fps=10) # Render as a GIF image, optionally save to file
-
#d.as_mp4('orbig.mp4', fps=60) # Render as an MP4 video, optionally save to file
+
#d.as_mp4('orbig.mp4', fps=60, verbose=True) # Render as an MP4 video, optionally save to file
+
#d.as_spritesheet('orbit-spritesheet.png', row_length=10, fps=3) # Render as a spritesheet
d.display_inline() # Display as interactive SVG
```
···
with draw.frame_animate_jupyter(draw_frame, delay=0.05) as anim:
# Or:
-
#with draw.animate_video('example6.gif', draw_frame, duration=0.05
-
# ) as anim:
+
#with draw.frame_animate_video('example6.gif', draw_frame, duration=0.05) as anim:
+
# Or:
+
#with draw.frame_animate_spritesheet('example6.png', draw_frame, row_length=10) as anim:
# Add each frame to the animation
for i in range(20):
anim.draw_frame(i/10)
···
anim.draw_frame(i/10)
```
-
![Example output image](https://raw.githubusercontent.com/cduck/drawsvg/master/examples/example6.gif)
+
GIF:
+
+
![Example output gif](https://raw.githubusercontent.com/cduck/drawsvg/master/examples/example6.gif)
+
+
Spritesheet (usable in most 2D game engines):
+
+
![Example output spritesheet](https://raw.githubusercontent.com/cduck/drawsvg/master/examples/example6.png)
### Asynchronous Frame-based Animation in Jupyter
```python
···
![Example output image](https://raw.githubusercontent.com/cduck/drawsvg/master/examples/example7.gif)
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)
---
+9
docs/img/01_circ.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="300" height="100" viewBox="0 0 300 100">
+
<defs>
+
</defs>
+
<circle cx="50" cy="50" r="40" />
+
<circle cx="150" cy="50" r="40" stroke="black" fill="none" />
+
<circle cx="250" cy="50" r="40" stroke="black" fill="none" stroke-width="15" />
+
</svg>
+9
docs/img/01_ellip.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="300" height="100" viewBox="0 0 300 100">
+
<defs>
+
</defs>
+
<ellipse cx="50" cy="50" rx="50" ry="30" />
+
<ellipse cx="160" cy="50" rx="50" ry="30" stroke="black" fill="none" />
+
<ellipse cx="250" cy="50" rx="30" ry="45" stroke="black" fill="none" />
+
</svg>
+7
docs/img/01_line.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="100" height="100" viewBox="0 0 100 100">
+
<defs>
+
</defs>
+
<path d="M30,30 L90,90" stroke="black" />
+
</svg>
+7
docs/img/01_multilines.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="100" height="100" viewBox="0 0 100 100">
+
<defs>
+
</defs>
+
<path d="M10,90 L10,10 L80,90 L80,10" fill="none" stroke="black" />
+
</svg>
+7
docs/img/01_multilines2.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="300" height="100" viewBox="0 0 300 100">
+
<defs>
+
</defs>
+
<path d="M30,80 L40,20 L50,80 L60,20 L70,80 L80,20 L90,80 L100,20 L110,80 L120,20 L130,80 L140,20 L150,80 L160,20 L170,80 L180,20 L190,80 L200,20 L210,80 L220,20" stroke="black" stroke-width="5" fill="none" />
+
</svg>
+7
docs/img/01_polygon.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="100" height="100" viewBox="0 0 100 100">
+
<defs>
+
</defs>
+
<path d="M48,16 L16,96 L96,48 L0,48 L88,96 Z" stroke="black" fill="none" />
+
</svg>
+10
docs/img/01_rect.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="500" height="200" viewBox="0 0 500 200">
+
<defs>
+
</defs>
+
<rect x="10" y="10" width="90" height="150" />
+
<rect x="120" y="10" width="60" height="120" fill="none" stroke="black" />
+
<rect x="210" y="10" width="75" height="90" fill="#0000ff" stroke="red" stroke-width="7" stroke-opacity="0.5" />
+
<rect x="300" y="10" width="105" height="60" fill="yellow" fill-opacity="0.5" stroke="green" stroke-width="2" stroke-dasharray="5,2" />
+
</svg>
+11
docs/img/01_rectround.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="500" height="200" viewBox="0 0 500 200">
+
<defs>
+
</defs>
+
<rect x="10" y="10" width="80" height="180" rx="10" ry="10" stroke="black" fill="none" />
+
<rect x="110" y="10" width="80" height="180" ry="20" stroke="black" fill="none" />
+
<rect x="210" y="10" width="80" height="180" rx="40" stroke="black" fill="none" />
+
<rect x="310" y="10" width="80" height="180" rx="30" ry="10" stroke="black" fill="none" />
+
<rect x="410" y="10" width="80" height="180" rx="10" ry="30" stroke="black" fill="none" />
+
</svg>
+9
docs/img/02_dash.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="110" height="40" viewBox="0 0 110 40">
+
<defs>
+
</defs>
+
<path d="M10,10 L100,10" stroke="black" stroke-width="2" stroke-dasharray="9,5" />
+
<path d="M10,20 L100,20" stroke="black" stroke-width="2" stroke-dasharray="5,3,9,2" />
+
<path d="M10,30 L100,30" stroke="black" stroke-width="2" stroke-dasharray="9,3,5" />
+
</svg>
+16
docs/img/02_foso.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="300" height="100" viewBox="0 0 300 100">
+
<defs>
+
</defs>
+
<path d="M0,10 L290,10" stroke="black" stroke-width="5" stroke-opacity="0.1" />
+
<rect x="0" y="70" width="50" height="50" fill="red" fill-opacity="0.1" />
+
<path d="M0,20 L290,20" stroke="black" stroke-width="5" stroke-opacity="0.30000000000000004" />
+
<rect x="60" y="70" width="50" height="50" fill="red" fill-opacity="0.30000000000000004" />
+
<path d="M0,30 L290,30" stroke="black" stroke-width="5" stroke-opacity="0.5" />
+
<rect x="120" y="70" width="50" height="50" fill="red" fill-opacity="0.5" />
+
<path d="M0,40 L290,40" stroke="black" stroke-width="5" stroke-opacity="0.7" />
+
<rect x="180" y="70" width="50" height="50" fill="red" fill-opacity="0.7" />
+
<path d="M0,50 L290,50" stroke="black" stroke-width="5" stroke-opacity="0.9" />
+
<rect x="240" y="70" width="50" height="50" fill="red" fill-opacity="0.9" />
+
</svg>
+11
docs/img/02_fsc.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="100" height="60" viewBox="0 0 100 60">
+
<defs>
+
</defs>
+
<path d="M10,10 L80,10" stroke="red" stroke-width="5" />
+
<path d="M10,20 L80,20" stroke="#9f9" stroke-width="5" />
+
<path d="M10,30 L80,30" stroke="#9999ff" stroke-width="5" />
+
<path d="M10,40 L80,40" stroke="rgb(255,128,64)" stroke-width="5" />
+
<path d="M10,50 L80,50" stroke="rgb(60%,20%,60%)" stroke-width="5" />
+
</svg>
+12
docs/img/02_join.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="300" height="100" viewBox="0 0 300 100">
+
<defs>
+
</defs>
+
<path d="M0,20 L300,20" stroke="gray" />
+
<g stroke-width="20" stroke="black" fill="none">
+
<path d="M10,80 L50,20 L90,80" stroke-linejoin="miter" />
+
<path d="M110,80 L150,20 L190,80" stroke-linejoin="round" />
+
<path d="M210,80 L250,20 L290,80" stroke-linejoin="bevel" />
+
</g>
+
</svg>
+11
docs/img/02_linecap.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="150" height="100" viewBox="0 0 150 100">
+
<defs>
+
</defs>
+
<path d="M10,15 L50,15" stroke="black" stroke-width="15" stroke-linecap="butt" />
+
<path d="M10,45 L50,45" stroke="black" stroke-width="15" stroke-linecap="round" />
+
<path d="M10,75 L50,75" stroke="black" stroke-width="15" stroke-linecap="square" />
+
<path d="M10,0 L10,100" stroke="#999" />
+
<path d="M50,0 L50,100" stroke="#999" />
+
</svg>
+12
docs/img/02_mlimit.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="300" height="100" viewBox="0 0 300 100">
+
<defs>
+
</defs>
+
<path d="M0,30 L300,30" stroke="gray" />
+
<g stroke-width="20" stroke="black" fill="none" stroke-linejoin="miter">
+
<path d="M10,90 L40,30 L70,90" />
+
<path d="M100,90 L130,30 L160,90" stroke-miterlimit="2.3" />
+
<path d="M190,90 L220,30 L250,90" stroke-miterlimit="1" />
+
</g>
+
</svg>
+26
docs/img/02_strokewdth.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="320" height="100" viewBox="0 0 320 100">
+
<defs>
+
</defs>
+
<path d="M15,10 L15,90" stroke="black" stroke-width="11" />
+
<path d="M30,10 L30,90" stroke="black" stroke-width="10" />
+
<path d="M45,10 L45,90" stroke="black" stroke-width="9" />
+
<path d="M60,10 L60,90" stroke="black" stroke-width="8" />
+
<path d="M75,10 L75,90" stroke="black" stroke-width="7" />
+
<path d="M90,10 L90,90" stroke="black" stroke-width="6" />
+
<path d="M105,10 L105,90" stroke="black" stroke-width="5" />
+
<path d="M120,10 L120,90" stroke="black" stroke-width="4" />
+
<path d="M135,10 L135,90" stroke="black" stroke-width="3" />
+
<path d="M150,10 L150,90" stroke="black" stroke-width="2" />
+
<path d="M165,10 L165,90" stroke="black" stroke-width="1" />
+
<path d="M180,10 L180,90" stroke="black" stroke-width="2" />
+
<path d="M195,10 L195,90" stroke="black" stroke-width="3" />
+
<path d="M210,10 L210,90" stroke="black" stroke-width="4" />
+
<path d="M225,10 L225,90" stroke="black" stroke-width="5" />
+
<path d="M240,10 L240,90" stroke="black" stroke-width="6" />
+
<path d="M255,10 L255,90" stroke="black" stroke-width="7" />
+
<path d="M270,10 L270,90" stroke="black" stroke-width="8" />
+
<path d="M285,10 L285,90" stroke="black" stroke-width="9" />
+
<path d="M300,10 L300,90" stroke="black" stroke-width="10" />
+
</svg>
+10
docs/img/03_pA.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="200" viewBox="0 0 400 200" stroke-width="3" fill="none">
+
<defs>
+
</defs>
+
<path d="M125,75 A100,50,0,0,0,225,125" stroke="red" />
+
<path d="M125,75 A100,50,0,0,1,225,125" stroke="blue" />
+
<path d="M125,75 A100,50,0,1,0,225,125" stroke="rgb(0 80 255)" stroke-dasharray="5 3" />
+
<path d="M125,75 A100,50,0,1,1,225,125" stroke="rgb(255 80 0)" stroke-dasharray="5 3" />
+
</svg>
+48
docs/img/03_pC.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="650" height="100" viewBox="0 0 650 100">
+
<defs>
+
</defs>
+
<path d="M40,50 C10,10,140,10,110,50" stroke="black" fill="none" stroke-width="3" transform="translate(0,0)" />
+
<g stroke="gray" fill="gray" stroke-width="1" transform="translate(0,0)">
+
<circle cx="10" cy="10" r="2" />
+
<circle cx="140" cy="10" r="2" />
+
<path d="M40,50 L10,10" />
+
<path d="M110,50 L140,10" />
+
</g>
+
<path d="M40,50 C60,10,90,10,110,50" stroke="black" fill="none" stroke-width="3" transform="translate(100,0)" />
+
<g stroke="gray" fill="gray" stroke-width="1" transform="translate(100,0)">
+
<circle cx="60" cy="10" r="2" />
+
<circle cx="90" cy="10" r="2" />
+
<path d="M40,50 L60,10" />
+
<path d="M110,50 L90,10" />
+
</g>
+
<path d="M40,50 C110,10,40,10,110,50" stroke="black" fill="none" stroke-width="3" transform="translate(200,0)" />
+
<g stroke="gray" fill="gray" stroke-width="1" transform="translate(200,0)">
+
<circle cx="110" cy="10" r="2" />
+
<circle cx="40" cy="10" r="2" />
+
<path d="M40,50 L110,10" />
+
<path d="M110,50 L40,10" />
+
</g>
+
<path d="M40,50 C110,10,40,10,110,50" stroke="black" fill="none" stroke-width="3" transform="translate(300,0)" />
+
<g stroke="gray" fill="gray" stroke-width="1" transform="translate(300,0)">
+
<circle cx="110" cy="10" r="2" />
+
<circle cx="40" cy="10" r="2" />
+
<path d="M40,50 L110,10" />
+
<path d="M110,50 L40,10" />
+
</g>
+
<path d="M40,50 C60,10,90,90,110,50" stroke="black" fill="none" stroke-width="3" transform="translate(400,0)" />
+
<g stroke="gray" fill="gray" stroke-width="1" transform="translate(400,0)">
+
<circle cx="60" cy="10" r="2" />
+
<circle cx="90" cy="90" r="2" />
+
<path d="M40,50 L60,10" />
+
<path d="M110,50 L90,90" />
+
</g>
+
<path d="M40,50 C110,10,40,90,110,50" stroke="black" fill="none" stroke-width="3" transform="translate(500,0)" />
+
<g stroke="gray" fill="gray" stroke-width="1" transform="translate(500,0)">
+
<circle cx="110" cy="10" r="2" />
+
<circle cx="40" cy="90" r="2" />
+
<path d="M40,50 L110,10" />
+
<path d="M110,50 L40,90" />
+
</g>
+
</svg>
+8
docs/img/03_pHV.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="100" height="60" viewBox="0 0 100 60">
+
<defs>
+
</defs>
+
<path d="M10,10 H100 M10,20 H100 V50" stroke="black" fill="none" id="pathHV0" />
+
<use xlink:href="#pathHV0" />
+
</svg>
+11
docs/img/03_pL.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="100" height="70" viewBox="0 0 100 70">
+
<defs>
+
</defs>
+
<g stroke="black" fill="none">
+
<path d="M10,10 L100,10" />
+
<path d="M10,20 L100,20 L100,50" />
+
<path d="M40,60 L10,60 L40,42 M60,60 L90,60 L60,42" />
+
</g>
+
</svg>
+21
docs/img/03_pQ.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="700" height="130" viewBox="0 0 700 130">
+
<defs>
+
<path d="M30,75 Q240,30,300,120" stroke="black" fill="none" stroke-width="3" id="pathQ0" />
+
</defs>
+
<use xlink:href="#pathQ0" />
+
<use xlink:href="#pathQ0" x="300" y="0" />
+
<g stroke="gray" fill="gray">
+
<circle cx="330" cy="75" r="3" />
+
<circle cx="600" cy="120" r="3" />
+
<circle cx="540" cy="30" r="3" />
+
<path d="M330,75 L540,30" />
+
<path d="M540,30 L600,120" />
+
<path d="M330,75 L600,120" stroke-dasharray="5,5" />
+
<circle cx="435" cy="52.5" r="3" />
+
<circle cx="570" cy="75" r="3" />
+
<path d="M435,52.5 L570,75" />
+
<circle cx="502.5" cy="63.75" r="4" fill="none" />
+
</g>
+
</svg>
+16
docs/img/03_pS.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="250" height="150" viewBox="0 0 250 150">
+
<defs>
+
</defs>
+
<path d="M30,100 C50,30,70,50,100,100 S150,40,200,80" stroke="black" fill="none" stroke-width="3" />
+
<circle cx="30" cy="100" r="4" />
+
<circle cx="50" cy="30" r="2" stroke="gray" fill="gray" />
+
<path d="M30,100 L50,30" stroke="gray" />
+
<circle cx="100" cy="100" r="4" />
+
<circle cx="70" cy="50" r="2" stroke="gray" fill="gray" />
+
<path d="M100,100 L70,50" stroke="gray" />
+
<circle cx="200" cy="80" r="4" />
+
<circle cx="150" cy="40" r="2" stroke="gray" fill="gray" />
+
<path d="M200,80 L150,40" stroke="gray" />
+
</svg>
+8
docs/img/03_pT.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="0 0 400 100">
+
<defs>
+
</defs>
+
<path d="M30,60 Q80,-10,100,60 Q130,25,200,40" stroke="black" fill="none" stroke-width="3" />
+
<path d="M30,60 Q80,-10,100,60 T200,40" stroke="black" fill="none" stroke-width="3" transform="translate(200,0)" />
+
</svg>
+8
docs/img/03_pZ.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="90" height="70" viewBox="0 0 90 70">
+
<defs>
+
</defs>
+
<path d="M10,10 h30 v50 h-30 Z M50,10 h30 v50 Z" stroke="black" fill="none" id="pathZ0" />
+
<use xlink:href="#pathZ0" />
+
</svg>
+16
docs/img/04_align.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="250" height="120" viewBox="0 0 250 120">
+
<defs>
+
</defs>
+
<path d="M75,100 L75,0" stroke="gray" />
+
<path d="M140,30 L250,30" stroke="gray" />
+
<path d="M140,60 L250,60" stroke="gray" />
+
<path d="M140,90 L250,90" stroke="gray" />
+
<text x="75" y="30" font-size="24" text-anchor="start">Start</text>
+
<text x="75" y="60" font-size="24" text-anchor="middle">Middle</text>
+
<text x="75" y="90" font-size="24" text-anchor="end">End</text>
+
<text x="150" y="30" font-size="24" dominant-baseline="auto">Auto</text>
+
<text x="150" y="60" font-size="24" dominant-baseline="middle">Middle</text>
+
<text x="150" y="90" font-size="24" dominant-baseline="hanging">Hanging</text>
+
</svg>
+13
docs/img/04_fill.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="550" height="370" viewBox="0 0 550 370">
+
<defs>
+
</defs>
+
<path d="M20,0 V370 M10,60 H500 M10,120 H500 M10,180 H500 M10,240 H500 M10,300 H500 M10,360 H500" stroke="gray" />
+
<text x="20" y="60" font-size="50">Simplest Text</text>
+
<text x="20" y="120" font-size="50" stroke="black">Outline / Filled</text>
+
<text x="20" y="180" font-size="50" stroke="black" stroke-width="5">Too big stroke</text>
+
<text x="20" y="240" font-size="50" stroke="black" stroke-width="0.5" fill="none">Outlined only</text>
+
<text x="20" y="300" font-size="50" stroke="black" fill="red">Outlined and colored</text>
+
<text x="20" y="360" font-size="50" fill="blue">Colored fill only</text>
+
</svg>
docs/img/04_fonts1.png

This is a binary file and will not be displayed.

docs/img/04_fonts2.png

This is a binary file and will not be displayed.

+14
docs/img/04_len.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="200" viewBox="0 0 400 200">
+
<defs>
+
</defs>
+
<text x="20" y="30" font-size="20" textLength="250" lengthAdjust="spacing">Two words</text>
+
<text x="20" y="70" font-size="20" textLength="250" lengthAdjust="spacingAndGlyphs">Two words</text>
+
<text x="20" y="110" font-size="20">Two words (normal length)</text>
+
<text x="20" y="150" font-size="20" textLength="80" lengthAdjust="spacing">Two words</text>
+
<text x="20" y="190" font-size="20" textLength="80" lengthAdjust="spacingAndGlyphs">Two words</text>
+
<path d="M20,10 L20,195" stroke="gray" />
+
<path d="M270,80 L270,10" stroke="gray" />
+
<path d="M100,130 L100,195" stroke="gray" />
+
</svg>
+8
docs/img/04_multiline_text.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="200" height="100" viewBox="0 0 200 100">
+
<defs>
+
</defs>
+
<text x="50" y="20" font-size="14" text-anchor="middle"><tspan x="50" dy="0em">this is</tspan><tspan x="50" dy="1em">a</tspan><tspan x="50" dy="1em">multiline text</tspan><tspan x="50" dy="1em">given as a</tspan><tspan x="50" dy="1em">list</tspan></text>
+
<text x="150" y="20" font-size="14" text-anchor="middle"><tspan x="150" dy="0em">this is</tspan><tspan x="150" dy="1em">a</tspan><tspan x="150" dy="1em">multiline text</tspan><tspan x="150" dy="1em">given as a</tspan><tspan x="150" dy="1em">string</tspan></text>
+
</svg>
+8
docs/img/04_mutiline_text.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="200" height="100" viewBox="0 0 200 100">
+
<defs>
+
</defs>
+
<text x="50" y="20" font-size="14" text-anchor="middle"><tspan x="50" dy="0em">this is</tspan><tspan x="50" dy="1em">a</tspan><tspan x="50" dy="1em">multiline text</tspan><tspan x="50" dy="1em">given as a</tspan><tspan x="50" dy="1em">list</tspan></text>
+
<text x="150" y="20" font-size="14" text-anchor="middle"><tspan x="150" dy="0em">this is</tspan><tspan x="150" dy="1em">a</tspan><tspan x="150" dy="1em">multiline text</tspan><tspan x="150" dy="1em">given as a</tspan><tspan x="150" dy="1em">string</tspan></text>
+
</svg>
+31
docs/img/04_path.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="500" height="200" viewBox="0 0 500 200">
+
<defs>
+
<path d="M30,50 C50,20,70,20,120,50 S150,10,200,50" stroke="gray" fill="none" id="textpath0" />
+
<path d="M250,30 L300,30 A30,30,0,0,1,330,60 L330,110" stroke="gray" fill="none" id="textpath1" />
+
<path d="M30,110 L100,110 L100,160" stroke="gray" fill="none" id="textpath2" />
+
<path d="M150,110 A40,30,0,1,0,230,110 M250,110 L270,140" stroke="gray" fill="none" id="textpath3" />
+
<path d="M330,130 L330,160 A30,30,0,0,1,300,180 L200,180" stroke="gray" fill="none" id="textpath4" />
+
</defs>
+
<use xlink:href="#textpath0" />
+
<use xlink:href="#textpath1" />
+
<use xlink:href="#textpath2" />
+
<use xlink:href="#textpath3" />
+
<use xlink:href="#textpath4" />
+
<text font-size="14"><textPath xlink:href="#textpath0">
+
<tspan dy="0em">Following a cubic Bรฉzier curve</tspan>
+
</textPath></text>
+
<text font-size="14"><textPath xlink:href="#textpath1">
+
<tspan dy="0em">Going 'round the bend</tspan>
+
</textPath></text>
+
<text font-size="14"><textPath xlink:href="#textpath2">
+
<tspan dy="0em">Making a quick turn</tspan>
+
</textPath></text>
+
<text font-size="14"><textPath xlink:href="#textpath3">
+
<tspan dy="0em">Text along a broken path</tspan>
+
</textPath></text>
+
<text font-size="14" offset="50%" text-anchor="middle"><textPath xlink:href="#textpath4" startOffset="50%">
+
<tspan dy="0em">centered</tspan>
+
</textPath></text>
+
</svg>
+8
docs/img/04_rot.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="200" height="100" viewBox="0 0 200 100">
+
<defs>
+
</defs>
+
<text x="20" y="20" font-size="20" letter-spacing="20" rotate="90">Rotate</text>
+
<text x="20" y="80" font-size="20" letter-spacing="20" rotate="0 90 180 270">Rotate</text>
+
</svg>
+7
docs/img/04_rot2.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="200" height="80" viewBox="0 0 200 80">
+
<defs>
+
</defs>
+
<text x="20" y="50" font-size="20" letter-spacing="20"><tspan rotate="68">R</tspan><tspan rotate="50 20">OT</tspan><tspan rotate="291 32 130">ATE</tspan></text>
+
</svg>
+7
docs/img/04_tspan.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="600" height="50" viewBox="0 0 600 50">
+
<defs>
+
</defs>
+
<text x="10" y="40" font-size="24">Switch among <tspan font-style="italic">italic</tspan><tspan>, normal, and </tspan><tspan font-weight="bold">bold</tspan><tspan> text.</tspan></text>
+
</svg>
+7
docs/img/04_tspan2.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="300" height="160" viewBox="0 0 300 160">
+
<defs>
+
</defs>
+
<text x="10" y="30" font-size="24">F<tspan dy="5">a</tspan><tspan dy="31" dx="21">l</tspan><tspan dy="89" dx="54">l</tspan></text>
+
</svg>
+7
docs/img/04_tspan3.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="300" height="160" viewBox="0 0 300 160">
+
<defs>
+
</defs>
+
<text x="10" y="30" font-size="24" dx="0,0,21,54" dy="0,5,21,54">Fall</text>
+
</svg>
+16
docs/img/04_weight.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="600" height="200" viewBox="0 0 600 200">
+
<defs>
+
</defs>
+
<text x="20" y="35" font-size="30" font-weight="bold">bold</text>
+
<text x="20" y="75" font-size="30" font-style="italic">italic</text>
+
<text x="20" y="115" font-size="30" text-decoration="underline">under</text>
+
<text x="20" y="155" font-size="30" text-decoration="overline">over</text>
+
<text x="20" y="195" font-size="30" text-decoration="line-through">through</text>
+
<text x="200" y="35" font-size="30">normal word space</text>
+
<text x="200" y="75" font-size="30" word-spacing="10">more word space</text>
+
<text x="200" y="115" font-size="30" word-spacing="-5">less word space</text>
+
<text x="200" y="155" font-size="30" letter-spacing="8">wide letter space</text>
+
<text x="200" y="195" font-size="30" letter-spacing="-2">narrow letter space</text>
+
</svg>
+12
docs/img/05_clip.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="200" height="200" viewBox="0 0 200 200">
+
<defs>
+
<clipPath id="clip0">
+
<rect x="100" y="100" width="100" height="100" />
+
</clipPath>
+
</defs>
+
<rect x="100" y="100" width="100" height="100" stroke="gray" fill="none" />
+
<circle cx="100" cy="100" r="100" fill="none" stroke="gray" stroke-dasharray="5 5" />
+
<circle cx="100" cy="100" r="100" fill="cyan" clip-path="url(#clip0)" />
+
</svg>
+12
docs/img/05_clip2.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="600" height="200" viewBox="0 0 600 200">
+
<defs>
+
<path d="M150,150 L68,145 L32,65 L60,126 L230,120 L194,53 L48,124 L14,99 L221,155 L1,178 L228,68 L117,151 L52,81 L15,5 L13,166 L277,2 L195,175 L110,108 L14,135 L113,195 L224,126 L283,59 L176,59 L112,194 L235,74 L11,106 L284,164 L51,47 L151,30 L170,184 L256,108 L259,171 L97,77 L145,150 L255,129 L201,150 L17,122 L124,190 L206,106 L88,93 L280,179" stroke="black" stroke-width="2" fill="none" id="clip20" />
+
<clipPath id="clip21">
+
<circle cx="150" cy="100" r="75" />
+
</clipPath>
+
</defs>
+
<use xlink:href="#clip20" />
+
<use xlink:href="#clip20" x="300" y="0" clip-path="url(#clip21)" />
+
</svg>
+29
docs/img/05_clip3.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="300" height="300" viewBox="0 0 300 300">
+
<defs>
+
<g id="clip30">
+
<rect x="0" y="50" width="90" height="60" fill="#999" />
+
<circle cx="25" cy="25" r="25" fill="#666" />
+
<path d="M30,0 L80,0 L80,100 Z" fill="#ccc" />
+
</g>
+
<path d="M5,55 C25,5,45,-25,75,55 C85,85,20,105,40,55 Z" stroke="black" stroke-width="1" stroke-dasharray="3 2" fill="none" id="clip31" />
+
<clipPath id="clip32">
+
<use xlink:href="#clip31" x="0" y="0" />
+
</clipPath>
+
<text x="20" y="20" font-size="48" font-weight="bold" transform="rotate(60)" stroke="black" stroke-width="1" stroke-dasharray="3 2" fill="none" id="clip33">CLIP</text>
+
<clipPath id="clip34">
+
<use xlink:href="#clip33" x="0" y="0" />
+
</clipPath>
+
</defs>
+
<use xlink:href="#clip30" x="0" y="0" clip-path="url(#clip32)" />
+
<g transform="translate(100,0)">
+
<use xlink:href="#clip30" />
+
<use xlink:href="#clip31" x="0" y="0" />
+
</g>
+
<use xlink:href="#clip30" x="0" y="150" clip-path="url(#clip34)" />
+
<g transform="translate(100,150)">
+
<use xlink:href="#clip30" />
+
<use xlink:href="#clip33" x="0" y="0" />
+
</g>
+
</svg>
+11
docs/img/05_lingrad.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="200" height="80" viewBox="0 0 200 80">
+
<defs>
+
<linearGradient x1="150" y1="0" x2="0" y2="0" gradientUnits="userSpaceOnUse" id="grad10">
+
<stop offset="0" stop-color="green" />
+
<stop offset="1" stop-color="yellow" />
+
</linearGradient>
+
</defs>
+
<rect x="10" y="10" width="150" height="60" stroke="black" fill="url(#grad10)" />
+
</svg>
+15
docs/img/05_mask.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="200" height="100" viewBox="0 0 200 100">
+
<defs>
+
<linearGradient x1="0" y1="0" x2="1" y2="0" gradientUnits="objectBoundingBox" id="mask10">
+
<stop offset="0" stop-color="white" />
+
<stop offset="1" stop-color="black" />
+
</linearGradient>
+
<mask id="mask11">
+
<rect x="30" y="0" width="100" height="100" fill="url(#mask10)" />
+
</mask>
+
</defs>
+
<rect x="0" y="0" width="200" height="100" fill="cyan" stroke="blue" stroke-width="2" />
+
<rect x="0" y="0" width="200" height="100" fill="pink" stroke="red" stroke-width="2" mask="url(#mask11)" />
+
</svg>
+38
docs/img/05_mask2.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="250" height="150" viewBox="0 0 250 150">
+
<defs>
+
<mask maskContentUnits="objectBoundingBox" id="mask20">
+
<rect x="0" y="0" width="1" height="1" fill="#f00" />
+
</mask>
+
<mask maskContentUnits="objectBoundingBox" id="mask21">
+
<rect x="0" y="0" width="1" height="1" fill="#0f0" />
+
</mask>
+
<mask maskContentUnits="objectBoundingBox" id="mask22">
+
<rect x="0" y="0" width="1" height="1" fill="#00f" />
+
</mask>
+
<mask maskContentUnits="objectBoundingBox" id="mask23">
+
<rect x="0" y="0" width="1" height="1" fill="#fff" />
+
</mask>
+
</defs>
+
<rect x="10" y="10" width="50" height="50" fill="#f00" />
+
<rect x="70" y="10" width="50" height="50" fill="#0f0" />
+
<rect x="130" y="10" width="50" height="50" fill="#00f" />
+
<rect x="190" y="10" width="50" height="50" fill="#fff" stroke="black" />
+
<g mask="url(#mask20)">
+
<circle cx="35" cy="115" r="25" fill="black" />
+
<text x="35" y="80" font-size="14" text-anchor="middle">Red</text>
+
</g>
+
<g mask="url(#mask21)">
+
<circle cx="95" cy="115" r="25" fill="black" />
+
<text x="95" y="80" font-size="14" text-anchor="middle">Green</text>
+
</g>
+
<g mask="url(#mask22)">
+
<circle cx="155" cy="115" r="25" fill="black" />
+
<text x="155" y="80" font-size="14" text-anchor="middle">Blue</text>
+
</g>
+
<g mask="url(#mask23)">
+
<circle cx="215" cy="115" r="25" fill="black" />
+
<text x="215" y="80" font-size="14" text-anchor="middle">White</text>
+
</g>
+
</svg>
+34
docs/img/05_mask3.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="250" height="100" viewBox="0 0 250 100">
+
<defs>
+
<mask maskContentUnits="objectBoundingBox" id="mask30">
+
<rect x="0" y="0" width="1" height="1" fill-opacity="1.0" fill="white" />
+
</mask>
+
<mask maskContentUnits="objectBoundingBox" id="mask31">
+
<rect x="0" y="0" width="1" height="1" fill-opacity="0.75" fill="white" />
+
</mask>
+
<mask maskContentUnits="objectBoundingBox" id="mask32">
+
<rect x="0" y="0" width="1" height="1" fill-opacity="0.5" fill="white" />
+
</mask>
+
<mask maskContentUnits="objectBoundingBox" id="mask33">
+
<rect x="0" y="0" width="1" height="1" fill-opacity="0.25" fill="white" />
+
</mask>
+
</defs>
+
<g mask="url(#mask30)">
+
<circle cx="35" cy="35" r="25" />
+
<text x="35" y="80" font-size="14" text-anchor="middle">100%</text>
+
</g>
+
<g mask="url(#mask31)">
+
<circle cx="95" cy="35" r="25" />
+
<text x="95" y="80" font-size="14" text-anchor="middle">50%</text>
+
</g>
+
<g mask="url(#mask32)">
+
<circle cx="155" cy="35" r="25" />
+
<text x="155" y="80" font-size="14" text-anchor="middle">50%</text>
+
</g>
+
<g mask="url(#mask33)">
+
<circle cx="215" cy="35" r="25" />
+
<text x="215" y="80" font-size="14" text-anchor="middle">25%</text>
+
</g>
+
</svg>
+11
docs/img/05_radgrad.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="200" viewBox="0 0 400 200">
+
<defs>
+
<radialGradient cx="200" cy="100" r="100" gradientUnits="userSpaceOnUse" id="grad20">
+
<stop offset="0" stop-color="green" stop-opacity="1" />
+
<stop offset="1" stop-color="orange" stop-opacity="1" />
+
</radialGradient>
+
</defs>
+
<rect x="0" y="0" width="100%" height="100%" fill="url(#grad20)" />
+
</svg>
+24
docs/img/06_group.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="240" height="120" viewBox="0 0 240 120">
+
<defs>
+
</defs>
+
<g id="house" fill="none" stroke="black">
+
<rect x="6" y="50" width="60" height="60" />
+
<path d="M6,50 L36,9 L66,50" />
+
<path d="M36,110 L36,80 L50,80 L50,110" />
+
</g>
+
<g id="man" fill="none" stroke="blue">
+
<circle cx="85" cy="56" r="10" />
+
<path d="M85,66 L85,80" />
+
<path d="M76,104 L85,80 L94,104" />
+
<path d="M76,70 L85,76 L94,70" />
+
</g>
+
<g id="woman" fill="none" stroke="red">
+
<circle cx="110" cy="56" r="10" />
+
<path d="M110,66 L110,80 L100,90 L120,90 L110,80" />
+
<path d="M104,104 L108,90" />
+
<path d="M112,90 L116,104" />
+
<path d="M101,70 L110,76 L119,70" />
+
</g>
+
</svg>
+7
docs/img/06_imag.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="200" height="200" viewBox="0 0 200 200">
+
<defs>
+
</defs>
+
<image x="0" y="0" width="200" height="200" xlink:href="data:image/png;base64,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" />
+
</svg>
+14
docs/img/06_use.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="220" height="170" viewBox="0 0 220 170">
+
<defs>
+
<g id="house" fill="none" stroke="black">
+
<rect x="6" y="50" width="60" height="60" />
+
<path d="M6,50 L36,9 L66,50" />
+
<path d="M36,110 L36,80 L50,80 L50,110" />
+
</g>
+
</defs>
+
<use xlink:href="#house" />
+
<use xlink:href="#house" x="100" y="50" />
+
<use xlink:href="#house" x="150" y="20" />
+
</svg>
+9
docs/img/07_cart1.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="100" height="100" viewBox="0 0 100 100">
+
<defs>
+
</defs>
+
<path d="M0,100 L0,0 L100,0" fill="none" stroke="green" stroke-width="2" />
+
<path d="M40,40 L100,40 L70,70 L40,70" fill="silver" stroke="black" stroke-width="2" />
+
<text x="5" y="95" font-size="12">downward y</text>
+
</svg>
+11
docs/img/07_cart2.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="100" height="100" viewBox="0 0 100 100">
+
<defs>
+
</defs>
+
<g transform="translate(0,100) scale(1,-1)">
+
<path d="M0,100 L0,0 L100,0" fill="none" stroke="green" stroke-width="2" />
+
<path d="M40,40 L100,40 L70,70 L40,70" fill="silver" stroke="black" stroke-width="2" />
+
<text x="5" y="95" font-size="12">upward y</text>
+
</g>
+
</svg>
+9
docs/img/07_cart3.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="100" height="100" viewBox="0 0 100 100" transform="scale(1,-1)">
+
<defs>
+
</defs>
+
<path d="M0,100 L0,0 L100,0" fill="none" stroke="green" stroke-width="2" />
+
<path d="M40,40 L100,40 L70,70 L40,70" fill="silver" stroke="black" stroke-width="2" />
+
<text x="5" y="95" font-size="12">upward y</text>
+
</svg>
+10
docs/img/07_rota.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="200" height="200" viewBox="0 0 200 200">
+
<defs>
+
</defs>
+
<rect x="0" y="0" width="200" height="200" stroke="gray" fill="none" />
+
<rect x="70" y="30" width="40" height="40" fill="silver" />
+
<rect x="70" y="30" width="40" height="40" fill="gray" transform="rotate(22.5)" />
+
<rect x="70" y="30" width="40" height="40" fill="black" transform="rotate(45)" />
+
</svg>
+17
docs/img/07_rota2.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="300" height="200" viewBox="0 0 300 200">
+
<defs>
+
<g id="arrow">
+
<path d="M110,100 L160,100" />
+
<path d="M160,100 L155,95 L155,105" />
+
</g>
+
</defs>
+
<circle cx="100" cy="100" r="3" fill="black" />
+
<use xlink:href="#arrow" x="0" y="0" stroke="black" fill="black" />
+
<g stroke="red" fill="red">
+
<use xlink:href="#arrow" x="0" y="0" transform="rotate (60,100,100)" />
+
<use xlink:href="#arrow" x="0" y="0" transform="rotate (-90,100,100)" />
+
<use xlink:href="#arrow" x="0" y="0" transform="rotate (-150,100,100)" />
+
</g>
+
</svg>
+12
docs/img/07_scalcent.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="300" height="200" viewBox="0 0 300 200">
+
<defs>
+
<rect x="70" y="80" width="60" height="40" stroke="black" fill="none" id="scalecenter0" />
+
</defs>
+
<circle cx="100" cy="100" r="4" fill="black" />
+
<use xlink:href="#scalecenter0" />
+
<use xlink:href="#scalecenter0" x="0" y="0" transform="translate(-100,-100) scale(2)" stroke-width="0.5" />
+
<use xlink:href="#scalecenter0" x="0" y="0" transform="translate(-150,-150) scale(2.5)" stroke-width="0.4" />
+
<use xlink:href="#scalecenter0" x="0" y="0" transform="translate(-200,-200) scale(3)" stroke-width="0.33" />
+
</svg>
+9
docs/img/07_scale.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="200" height="200" viewBox="0 0 200 200">
+
<defs>
+
<rect x="0" y="0" width="40" height="40" fill="none" stroke-width="2" id="scale10" />
+
</defs>
+
<use xlink:href="#scale10" x="10" y="10" stroke="black" />
+
<use xlink:href="#scale10" x="10" y="10" stroke="red" transform="scale(2)" />
+
</svg>
+9
docs/img/07_scale2.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="200" height="100" viewBox="0 0 200 100">
+
<defs>
+
<rect x="0" y="0" width="40" height="40" fill="none" stroke-width="2" id="scale20" />
+
</defs>
+
<use xlink:href="#scale20" x="10" y="10" stroke="black" />
+
<use xlink:href="#scale20" x="10" y="10" stroke="red" transform="scale(3,1.5)" />
+
</svg>
+23
docs/img/07_skew.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="200" height="100" viewBox="0 0 200 100" id-srefix="skew">
+
<defs>
+
</defs>
+
<g stroke="gray" stroke-dasharray="4 4">
+
<path d="M0,0 L200,0" />
+
<path d="M20,0 L20,90" />
+
<path d="M120,0 L120,90" />
+
</g>
+
<g transform="translate(20,0)">
+
<g transform="skewX(30)">
+
<path d="M50,0 L0,0 L0,50" stroke="black" fill="none" stroke-width="2" />
+
<text x="0" y="60" font-size="16">skewX</text>
+
</g>
+
</g>
+
<g transform="translate(120,0)">
+
<g transform="skewY(30)">
+
<path d="M50,0 L0,0 L0,50" stroke="black" fill="none" stroke-width="2" />
+
<text x="0" y="60" font-size="16">skewY</text>
+
</g>
+
</g>
+
</svg>
+8
docs/img/07_trans.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="200" height="200" viewBox="0 0 200 200">
+
<defs>
+
</defs>
+
<rect x="0" y="0" width="40" height="40" />
+
<rect x="0" y="0" width="40" height="40" fill="red" transform="translate(50,50)" />
+
</svg>
docs/img/example1.png

This is a binary file and will not be displayed.

+1307
docs/index.md
···
+
# Drawsvg Quick Reference
+
+
Repository: [https://github.com/cduck/drawsvg](https://github.com/cduck/drawsvg)
+
+
+
```python
+
import drawsvg as dw
+
```
+
+
+
## Canvas and Document Structure
+
+
```python
+
d = dw.Drawing(width, height, origin=(0, 0),
+
context: drawsvg.types.Context = None, animation_config=None,
+
id_prefix='d', **svg_args)
+
```
+
+
It is recommended to use a unique `id_prefix` for each svg if you embed multiple on a web page.
+
+
```python
+
d = dw.Drawing(400, 300, id_prefix='pic')
+
```
+
+
+
## Basic Shapes
+
+
### One Line
+
+
```python
+
dw.Line(sx, sy, ex, ey, **kwargs)
+
```
+
+
```python
+
line = dw.Line(30, 30, 90, 90, stroke='black')
+
d.append(line)
+
```
+
+
![svg](img/01_line.svg)
+
+
+
### Multiple Lines
+
+
This is SVG's `polyline` (but drawsvg renders as path with multiple L).
+
+
```python
+
dw.Lines(sx, sy, *points, close=False, **kwargs)
+
```
+
+
```python
+
lines = dw.Lines(10, 90, 10, 10, 80, 90, 80, 10,
+
fill='none', stroke='black')
+
d.append(lines)
+
```
+
+
![svg](img/01_multilines.svg)
+
+
+
```python
+
x = [30 + x*10 for x in range(20)]
+
y = [80, 20]*10
+
xy = [item for sublist in zip(x, y) for item in sublist]
+
d.append(dw.Lines(*xy, stroke='black', stroke_width=5, fill='none'))
+
```
+
+
![svg](img/01_multilines2.svg)
+
+
+
### Polygon
+
+
SVG `Polygon` is drawsvg `Lines` with `close=True`.
+
+
```python
+
polygon = dw.Lines(15, 10, 55, 10, 45, 20, 5, 20,
+
fill='red', stroke='black', close='true')
+
star = dw.Lines(48, 16, 16, 96, 96, 48, 0, 48, 88, 96,
+
stroke='black', fill='none', close='true')
+
d.append(star)
+
```
+
+
![svg](img/01_polygon.svg)
+
+
+
### Rectangle
+
+
```python
+
dw.Rectangle(x, y, width, height, **kwargs)
+
```
+
+
```python
+
# Black interior, no outline
+
d.append(dw.Rectangle(10, 10, 90, 150))
+
# No interior, black outline
+
d.append(dw.Rectangle(120, 10, 60, 120,
+
fill='none', stroke='black'))
+
# Blue interior, thick semi-transparent red outline
+
d.append(dw.Rectangle(210, 10, 75, 90,
+
fill='#0000ff', stroke='red',
+
stroke_width=7, stroke_opacity=0.5))
+
# Semi-transparent yellow interior, dashed green outline
+
d.append(dw.Rectangle(300, 10, 105, 60,
+
fill='yellow', fill_opacity=0.5,
+
stroke='green', stroke_width=2,
+
stroke_dasharray='5,2'))
+
```
+
+
![svg](img/01_rect.svg)
+
+
Rounded corners:
+
+
```python
+
# Define both rx and ry
+
d.append(dw.Rectangle(10, 10, 80, 180, rx='10', ry='10',
+
stroke='black', fill='none'))
+
# If only one is given, it applies to both
+
d.append(dw.Rectangle(110, 10, 80, 180, ry='20',
+
stroke='black', fill='none'))
+
d.append(dw.Rectangle(210, 10, 80, 180, rx='40',
+
stroke='black', fill='none'))
+
# Rx and ry unequal
+
d.append(dw.Rectangle(310, 10, 80, 180, rx='30', ry='10',
+
stroke='black', fill='none'))
+
d.append(dw.Rectangle(410, 10, 80, 180, rx='10', ry='30',
+
stroke='black', fill='none'))
+
```
+
+
![svg](img/01_rectround.svg)
+
+
+
### Circle
+
+
```python
+
dw.Circle(cx, cy, r, **kwargs)
+
```
+
+
cx and cy point to circle's center, r refer to its radius
+
+
```python
+
d.append(dw.Circle(50, 50, 40))
+
d.append(dw.Circle(150, 50, 40,
+
stroke='black', fill='none'))
+
d.append(dw.Circle(250, 50, 40,
+
stroke='black', fill='none',
+
stroke_width=15))
+
```
+
+
![svg](img/01_circ.svg)
+
+
+
### Ellipse
+
+
```python
+
dw.Ellipse(cx, cy, rx, ry, **kwarg)
+
```
+
(cx,cy) points to the center and (rx,ry) tells its radius
+
+
```python
+
d.append(dw.Ellipse(50, 50, 50, 30))
+
d.append(dw.Ellipse(160, 50, 50, 30,
+
stroke='black', fill='none'))
+
d.append(dw.Ellipse(250, 50, 30, 45,
+
stroke='black',fill='none'))
+
```
+
+
![svg](img/01_ellip.svg)
+
+
+
+
## Color and Painting Properties
+
+
For a full list, see [W3C specifications](https://www.w3.org/TR/SVG11/styling.html).
+
+
+
### fill and stroke\_color
+
+
Some color keyword names are:
+
aqua, black, blue, fuchsia, gray, green, lime, maroon, navy, olive, purple, red, silver, teal, white, and yellow.
+
+
Also supported is `#rrggbb`, `#rgb` (hexadecimal), or `rgb(R,G,B)` with 0-255 or with 0-100% for each value.
+
+
```python
+
c = ['red', '#9f9', '#9999ff', 'rgb(255,128,64)', 'rgb(60%,20%,60%)']
+
for i in range(5):
+
y = (i + 1)*10
+
d.append(dw.Line(10, y, 80, y,
+
stroke=c[i], stroke_width=5))
+
```
+
+
![svg](img/02_fsc.svg)
+
+
+
### fill\_opacity and stroke\_opacity
+
+
Value range from 0 = transparent to 1 = solid.
+
+
```python
+
for i in range(5):
+
y = (i + 1)*10
+
d.append(dw.Line(0, y, 290, y,
+
stroke='black', stroke_width=5,
+
stroke_opacity=i/5 + 0.1))
+
d.append(dw.Rectangle(i*60, 70, 50, 50,
+
fill='red', fill_opacity=i/5+0.1))
+
```
+
+
![svg](img/02_foso.svg)
+
+
+
### stroke\_dasharray
+
+
```python
+
# Nine-pixel dash, five-pixel gap
+
d.append(dw.Line(10, 10, 100, 10,
+
stroke='black', stroke_width=2,
+
stroke_dasharray='9,5'))
+
# Five-pixel dash, three-pixel gap, nine-pixel dash, two-pixel gap
+
d.append(dw.Line(10, 20, 100, 20,
+
stroke='black', stroke_width=2,
+
stroke_dasharray='5,3,9,2'))
+
# Odd number of entries alternates dashes and gaps
+
d.append(dw.Line(10, 30, 100, 30,
+
stroke='black', stroke_width=2,
+
stroke_dasharray='9,3,5'))
+
```
+
+
![svg](img/02_dash.svg)
+
+
+
### stroke\_width
+
+
```python
+
for i in range(20):
+
d.append(dw.Line((i+1)*15, 10, (i+1)*15, 90,
+
stroke='black', stroke_width=abs(10-i)+1))
+
```
+
+
![svg](img/02_strokewdth.svg)
+
+
+
### stroke\_linecap
+
+
`stroke_linecap` can be set to `butt`, `round`, or `square`.
+
Note that the latter two extend beyond the end coordinates.
+
+
```python
+
d.append(dw.Line(10, 15, 50, 15,
+
stroke='black', stroke_width=15,
+
stroke_linecap='butt'))
+
d.append(dw.Line(10, 45, 50, 45,
+
stroke='black', stroke_width=15,
+
stroke_linecap='round'))
+
d.append(dw.Line(10, 75, 50, 75,
+
stroke='black', stroke_width=15,
+
stroke_linecap='square'))
+
# Guide lines
+
d.append(dw.Lines(10, 0, 10, 100, stroke='#999'))
+
d.append(dw.Lines(50, 0, 50, 100, stroke='#999'))
+
```
+
+
![svg](img/02_linecap.svg)
+
+
+
### stroke\_linejoin
+
+
Define the way lines connect at a corner with `stroke-linejoin`: `miter` (pointed), `round`, or `bevel` (flat).
+
+
```python
+
d.append(dw.Line(0, 20, 300, 20, stroke='gray'))
+
g = dw.Group(stroke_width=20, stroke='black', fill='none')
+
g.append(dw.Lines(10, 80, 50, 20, 90, 80,
+
stroke_linejoin='miter'))
+
g.append(dw.Lines(110, 80, 150, 20, 190, 80,
+
stroke_linejoin='round'))
+
g.append(dw.Lines(210, 80, 250, 20, 290, 80,
+
stroke_linejoin='bevel'))
+
d.append(g)
+
```
+
+
![svg](img/02_join.svg)
+
+
+
### stroke\_miterlimit
+
+
When two line segments meet at a sharp angle and miter joins have been specified for `stroke-linejoin`,
+
it is possible for the miter to extend far beyond the thickness of the line stroking the path.
+
The `stroke-miterlimit` imposes a limit on the ratio of the miter length to the `stroke-width`.
+
When the limit is exceeded, the join is converted from a miter to a bevel.
+
(From [W3C doc](https://www.w3.org/TR/SVG11/painting.html#StrokeMiterlimitProperty))
+
+
```python
+
d.append(dw.Line(0, 30, 300, 30, stroke='gray'))
+
g = dw.Group(stroke_width=20, stroke='black',
+
fill='none', stroke_linejoin='miter')
+
g.append(dw.Lines(10, 90, 40, 30, 70, 90))
+
g.append(dw.Lines(100, 90, 130, 30, 160, 90,
+
stroke_miterlimit=2.3))
+
g.append(dw.Lines(190, 90, 220, 30, 250, 90,
+
stroke_miterlimit=1))
+
d.append(g)
+
```
+
+
![svg](img/02_mlimit.svg)
+
+
+
## Path
+
+
```python
+
path = dw.Path(**kwargs)
+
```
+
+
The following Path specifiers are also available as lowercase characters.
+
In that case, their movements are relative to current location.
+
+
+
### M: moveto
+
+
```python
+
path.M(x, y)
+
```
+
+
Move to `x, y` (and draw nothing).
+
+
+
### L: lineto
+
+
```python
+
path.L(x, y)
+
```
+
+
Draw a straight line to `x, y`.
+
+
```python
+
g = dw.Group(stroke='black', fill='none')
+
+
p = dw.Path()
+
p.M(10, 10).L(100, 10)
+
g.append(p)
+
+
p = dw.Path()
+
p.M(10, 20).L(100, 20).L(100, 50)
+
g.append(p)
+
+
p = dw.Path()
+
p.M(40, 60).L(10, 60).L(40, 42)
+
p.M(60, 60).L(90, 60).L(60, 42)
+
g.append(p)
+
+
d.append(g)
+
```
+
+
![svg](img/03_pL.svg)
+
+
+
### H: horizontal line
+
+
```python
+
path.H(x)
+
```
+
+
Draw a horizontal line to the new `x` location.
+
+
+
### V: vertical line
+
+
```python
+
path.V(y)
+
```
+
+
Draw a vertical line to the new `y` location.
+
+
```python
+
p = dw.Path(stroke='black', fill='none')
+
d.append(p.M(10, 10).H(100))
+
d.append(p.M(10, 20).H(100).V(50))
+
```
+
![svg](img/03_pHV.svg)
+
+
+
### Q: quadratic Bรฉzier curve (one control point)
+
+
```python
+
path.Q(x_ctl, y_ctl, x_end, y_end)
+
```
+
+
Draw a quadratic Bรฉzier curve from current location to `x_end, y_end` by means of `x_ctl, y_ctl`.
+
+
```python
+
# Curve only (left)
+
p = dw.Path(stroke='black', fill='none', stroke_width=3)
+
d.append(p.M(30, 75).Q(240, 30, 300, 120))
+
# With control point and construction lines
+
d.append(dw.Use(p, 300, 0))
+
g = dw.Group(stroke='gray', fill='gray')
+
g.append(dw.Circle(330, 75, 3))
+
g.append(dw.Circle(600, 120, 3))
+
g.append(dw.Circle(540, 30, 3))
+
g.append(dw.Line(330, 75, 540, 30))
+
g.append(dw.Line(540, 30, 600, 120))
+
g.append(dw.Line(330, 75, 600, 120, stroke_dasharray='5,5'))
+
g.append(dw.Circle(435, 52.5, 3))
+
g.append(dw.Circle(570, 75, 3))
+
g.append(dw.Line(435, 52.5, 570, 75))
+
g.append(dw.Circle(502.5, 63.75, 4, fill='none'))
+
d.append(g)
+
```
+
+
![svg](img/03_pQ.svg)
+
+
+
### T: smooth quadratic Bรฉzier curve (generated control point)
+
+
```python
+
path.T(x, y)
+
```
+
+
Draws a quadratic Bรฉzier curve from the current point to (x, y).
+
The control point is assumed to be the reflection of the control point on the previous command relative to the current point.
+
If there is no previous command or if the previous command was not a Q, q, T or t, assume the control point is coincident with the current point.
+
(From [W3C Doc](https://www.w3.org/TR/SVG11/paths.html#PathDataQuadraticBezierCommands))
+
+
```python
+
# Curve sequence (left)
+
p = dw.Path(stroke='black', fill='none', stroke_width=3)
+
d.append(p.M(30, 60).Q(80, -10, 100, 60).Q(130, 25, 200, 40))
+
# With smooth continuation (right)
+
p = dw.Path(stroke='black', fill='none', stroke_width=3,
+
transform='translate(200,0)')
+
d.append(p.M(30, 60).Q(80, -10, 100, 60).T(200, 40))
+
```
+
+
![svg](img/03_pT.svg)
+
+
+
### C: cubic Bรฉzier curve (two control points)
+
+
```python
+
path.C(x_ctl_1, y_ctl_1, x_ctl_2, y_ctl_2, x_end, y_end)
+
```
+
+
Draw a cubic Bรฉzier curve by means of two control points (one for start and one for end).
+
+
```python
+
pnt_1 = (40, 50)
+
pnt_2 = (110, 50)
+
ctl_1_x = (10, 60, 110, 110, 60, 110)
+
ctls_2 = ((140, 10), (90, 10), (40, 10), (40, 10), (90, 90), (40, 90))
+
+
for i in range(6):
+
trans = f'translate({i*100},0)'
+
p = dw.Path(stroke='black', fill='none',
+
stroke_width=3, transform=trans)
+
ctl_1 = (ctl_1_x[i], 10)
+
ctl_2 = ctls_2[i]
+
p.M(*pnt_1)
+
p.C(*ctl_1, *ctl_2, *pnt_2)
+
d.append(p)
+
g = dw.Group(stroke='gray', fill='gray',
+
stroke_width=1, transform=trans)
+
g.append(dw.Circle(*ctl_1, 2))
+
g.append(dw.Circle(*ctl_2, 2))
+
g.append(dw.Line(*pnt_1, *ctl_1))
+
g.append(dw.Line(*pnt_2, *ctl_2))
+
d.append(g)
+
```
+
+
![svg](img/03_pC.svg)
+
+
+
### S: smooth cubic Bรฉzier (one control point)
+
+
Similar to `T` in quadratic Bรฉzier curve. The first control point is calculated as reflection of the previous second control point.
+
+
```python
+
path.S(x_ctl_2, y_ctl_2, x_end, y_end)
+
```
+
+
```python
+
pnt_1 = (30, 100)
+
pnt_2 = (100, 100)
+
pnt_3 = (200, 80)
+
ctl_1 = (50, 30)
+
ctl_2 = (70, 50)
+
ctl_3 = (150, 40)
+
+
p = dw.Path(stroke='black', fill='none', stroke_width=3)
+
p.M(*pnt_1)
+
p.C(*ctl_1, *ctl_2, *pnt_2)
+
p.S(*ctl_3, *pnt_3)
+
d.append(p)
+
+
for pnt, ctl in zip((pnt_1, pnt_2, pnt_3), (ctl_1, ctl_2, ctl_3)):
+
d.append(dw.Circle(*pnt, 4))
+
d.append(dw.Circle(*ctl, 2, stroke='gray', fill='gray'))
+
d.append(dw.Line(*pnt, *ctl, stroke='gray'))
+
```
+
+
![svg](img/03_pS.svg)
+
+
+
### A: elliptical Arc
+
+
```python
+
path.A(rx, ry, rot, largeArc, sweep, ex, ey)
+
+
rx, ry: radius
+
rot: x-axis rotation
+
largeArc: True or False
+
sweep: True (positive) or False (negative) angle
+
ex, ey: end point
+
```
+
+
```python
+
p = dw.Path(stroke='red')
+
d.append(p.M(125, 75).A(100, 50, rot=0, large_arc=0, sweep=0, ex=225, ey=125))
+
p = dw.Path(stroke='blue')
+
d.append(p.M(125, 75).A(100, 50, rot=0, large_arc=0, sweep=1, ex=225, ey=125))
+
p = dw.Path(stroke='rgb(0 80 255)',stroke_dasharray='5 3')
+
d.append(p.M(125, 75).A(100, 50, rot=0, large_arc=1, sweep=0, ex=225, ey=125))
+
p = dw.Path(stroke='rgb(255 80 0)',stroke_dasharray='5 3')
+
d.append(p.M(125, 75).A(100, 50, rot=0, large_arc=1, sweep=1, ex=225, ey=125))
+
```
+
+
![svg](img/03_pA.svg)
+
+
+
### Z: closepath
+
+
```python
+
path.Z()
+
```
+
+
Close the path.
+
+
```python
+
p = dw.Path(stroke='black', fill='none')
+
d.append(p.M(10, 10).h(30).v(50).h(-30).Z())
+
d.append(p.M(50, 10).h(30).v(50).Z())
+
```
+
+
![svg](img/03_pZ.svg)
+
+
+
## Text
+
```python
+
dw.Text(text, fontSize, x=None, y=None, *, center=False,
+
line_height=1, line_offset=0, path=None,
+
start_offset=None, path_args=None, tspan_args=None,
+
cairo_fix=True, **kwargs)
+
```
+
+
### Fill and Outline
+
+
Default is black as fill color and no outline.
+
+
```python
+
# Reference lines
+
l = dw.Path(stroke='gray')
+
l.M(20, 0).V(370)
+
for i in range(1, 7):
+
l.M(10, i*60).H(500)
+
d.append(l)
+
+
d.append(dw.Text('Simplest Text', font_size=50, x=20, y=60))
+
d.append(dw.Text('Outline / Filled', font_size=50, x=20, y=120, stroke='black'))
+
d.append(dw.Text('Too big stroke', font_size=50, x=20, y=180, stroke='black', stroke_width=5))
+
d.append(dw.Text('Outlined only', font_size=50, x=20, y=240, stroke='black', stroke_width=0.5, fill='none'))
+
d.append(dw.Text('Outlined and colored', font_size=50, x=20, y=300, stroke='black', fill='red'))
+
d.append(dw.Text('Colored fill only', font_size=50, x=20, y=360, fill='blue'))
+
```
+
+
![svg](img/04_fill.svg)
+
+
+
### Weight, Style, Decoration, Spacing
+
+
```python
+
d.append(dw.Text('bold', font_size=30, x=20, y=35, font_weight='bold'))
+
d.append(dw.Text('italic', font_size=30, x=20, y=75, font_style='italic'))
+
d.append(dw.Text('under', font_size=30, x=20, y=115, text_decoration='underline'))
+
d.append(dw.Text('over', font_size=30, x=20, y=155, text_decoration='overline'))
+
d.append(dw.Text('through', font_size=30, x=20, y=195, text_decoration='line-through'))
+
d.append(dw.Text('normal word space', font_size=30, x=200, y=35))
+
d.append(dw.Text('more word space', font_size=30, x=200, y=75, word_spacing=10))
+
d.append(dw.Text('less word space', font_size=30, x=200, y=115, word_spacing=-5))
+
d.append(dw.Text('wide letter space', font_size=30, x=200, y=155, letter_spacing=8))
+
d.append(dw.Text('narrow letter space', font_size=30, x=200, y=195, letter_spacing=-2))
+
```
+
+
![svg](img/04_weight.svg)
+
+
+
### Text Alignment
+
+
Horizontal alignment (`text_anchor`) can be `'start'`, `'middle'` or `'end'`.
+
+
Vertical alignment (`dominant_baseline`) can be `'auto'`, `'middle'` or `'hanging'`
+
(and more, see [here](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/dominant-baseline)).
+
+
```python
+
d.append(dw.Line(75, 100, 75, 0, stroke='gray'))
+
d.append(dw.Line(140, 30, 250, 30, stroke='gray'))
+
d.append(dw.Line(140, 60, 250, 60, stroke='gray'))
+
d.append(dw.Line(140, 90, 250, 90, stroke='gray'))
+
d.append(dw.Text('Start', 24, 75, 30, text_anchor='start'))
+
d.append(dw.Text('Middle', 24, 75, 60, text_anchor='middle'))
+
d.append(dw.Text('End', 24, 75, 90, text_anchor='end'))
+
d.append(dw.Text('Auto', 24, 150, 30, dominant_baseline='auto'))
+
d.append(dw.Text('Middle', 24, 150, 60, dominant_baseline='middle'))
+
d.append(dw.Text('Hanging', 24, 150, 90, dominant_baseline='hanging'))
+
```
+
+
![svg](img/04_align.svg)
+
+
+
### TSpan
+
+
Continues a `Text` element.
+
+
```python
+
txt = dw.Text('Switch among ', 24, 10, 40)
+
txt.append(dw.TSpan('italic', font_style='italic'))
+
txt.append(dw.TSpan(', normal, and '))
+
txt.append(dw.TSpan('bold', font_weight='bold'))
+
txt.append(dw.TSpan(' text.'))
+
d.append(txt)
+
```
+
+
![svg](img/04_tspan.svg)
+
+
+
```python
+
txt = dw.Text('F', 24, 10, 30)
+
txt.append(dw.TSpan('a', dy=5))
+
txt.append(dw.TSpan('l', dy=31, dx=21))
+
txt.append(dw.TSpan('l', dy=89, dx=54))
+
d.append(txt)
+
```
+
+
![svg](img/04_tspan2.svg)
+
+
The same could be achieved by a list of dx/dy values:
+
+
```python
+
d.append(dw.Text('Fall', 24, 10, 30,
+
dx='0,0,21,54', dy='0,5,21,54'))
+
```
+
+
![svg](img/04_tspan3.svg)
+
+
+
### Rotate
+
+
Either one angle (degrees), or a list which is applied to all characters.
+
If the list is smaller than the number of characters, the last angle persists.
+
+
```python
+
d.append(dw.Text('Rotate', 20, 20, 20, letter_spacing=20, rotate='90'))
+
d.append(dw.Text('Rotate', 20, 20, 80, letter_spacing=20, rotate='0 90 180 270'))
+
```
+
+
![svg](img/04_rot.svg)
+
+
`TSpan` can also be used:
+
+
```python
+
import random
+
random.seed(1)
+
+
txt = dw.Text('', 20, 20, 50, letter_spacing=20)
+
txt.append(dw.TSpan('R', rotate=random.randrange(360)))
+
txt.append(dw.TSpan('OT', rotate='50 20'))
+
rotate = ' '.join([str(random.randrange(360)) for i in range(3)])
+
txt.append(dw.TSpan('ATE', rotate=rotate))
+
d.append(txt)
+
```
+
+
![svg](img/04_rot2.svg)
+
+
+
### Setting Text Length
+
+
```python
+
s = 'Two words'
+
d.append(dw.Text(s, 20, 20, 30, textLength=250, lengthAdjust='spacing'))
+
d.append(dw.Text(s, 20, 20, 70, textLength=250, lengthAdjust='spacingAndGlyphs'))
+
d.append(dw.Text(s+' (normal length)', 20, 20, 110))
+
d.append(dw.Text(s, 20, 20, 150, textLength=80, lengthAdjust='spacing'))
+
d.append(dw.Text(s, 20, 20, 190, textLength=80, lengthAdjust='spacingAndGlyphs'))
+
+
d.append(dw.Line(20, 10, 20, 195, stroke='gray'))
+
d.append(dw.Line(270, 80, 270, 10, stroke='gray'))
+
d.append(dw.Line(100, 130, 100, 195, stroke='gray'))
+
```
+
+
![svg](img/04_len.svg)
+
+
+
### Text on a Path
+
+
```python
+
curve_path = dw.Path(stroke='gray', fill='none')
+
curve_path.M(30, 50).C(50, 20, 70, 20, 120, 50).S(150, 10, 200, 50)
+
+
round_corner = dw.Path(stroke='gray', fill='none')
+
round_corner.M(250, 30).L(300, 30).A(30, 30, 0, 0, 1, 330, 60).L(330, 110)
+
+
sharp_corner = dw.Path(stroke='gray', fill='none')
+
sharp_corner.M(30, 110).L(100, 110).L(100, 160)
+
+
discontinuous = dw.Path(stroke='gray', fill='none')
+
discontinuous.M(150, 110).A(40, 30, 0, 1, 0, 230, 110).M(250, 110).L(270, 140)
+
+
center_curve = dw.Path(stroke='gray', fill='none')
+
center_curve.M(330, 130).L(330, 160).A(30, 30, 0, 0, 1, 300, 180).L(200, 180)
+
+
d.append(curve_path)
+
d.append(round_corner)
+
d.append(sharp_corner)
+
d.append(discontinuous)
+
d.append(center_curve)
+
+
t_cp = dw.Text('Following a cubic Bรฉzier curve', 14, path=curve_path)
+
t_rc = dw.Text("Going 'round the bend", 14, path=round_corner)
+
t_sc = dw.Text('Making a quick turn', 14, path=sharp_corner)
+
t_dc = dw.Text('Text along a broken path', 14, path=discontinuous)
+
t_ct = dw.Text('centered', 14, path=center_curve, offset='50%', text_anchor='middle')
+
+
d.append(t_cp)
+
d.append(t_rc)
+
d.append(t_sc)
+
d.append(t_dc)
+
d.append(t_ct)
+
```
+
+
![svg](img/04_path.svg)
+
+
+
### Multi Line Text
+
+
This is a particular feature of drawsvg: A list of strings as input for Text()
+
is rendered as multi-line text.
+
+
```python
+
tl = ['this is', 'a', 'multiline text', 'given as a', 'list']
+
d.append(dw.Text(tl, 14, 50, 20, text_anchor='middle'))
+
+
ts = 'this is\na\nmultiline text\ngiven as a\nstring'
+
d.append(dw.Text(ts, 14, 150, 20, text_anchor='middle'))
+
```
+
+
![svg](img/04_multiline_text.svg)
+
+
+
### Fonts
+
+
Specify fonts via `font_family`.
+
+
```python
+
d.append(dw.Text('Some text in Times New Roman.', 30, 10, 35, font_family='Times New Roman'))
+
d.append(dw.Text('Some text in Arial Black.', 30, 10, 75, font_family='Arial Black'))
+
d.append(dw.Text('Some text in Georgia.', 30, 10, 115, font_family='Georgia'))
+
```
+
+
![png](img/04_fonts1.png)
+
+
Specify a default font.
+
+
```python
+
d = dw.Drawing(600, 120, font_family='Times New Roman')
+
d.append(dw.Text('Some text in global setting (Times New Roman).', 30, 10, 35))
+
d.append(dw.Text('Some text in Arial Black.', 30, 10, 75, font_family='Arial Black'))
+
d.append(dw.Text('Some text in Georgia.', 30, 10, 115, font_family='Georgia'))
+
```
+
+
![png](img/04_fonts2.png)
+
+
+
+
## Gradient, Clip, Mask
+
+
### Linear Gradient
+
+
```python
+
gradient = dw.LinearGradient(x1, y1, x2, y2, gradientUnits='userSpaceOnUse', **kwargs)
+
gradient.add_stop(offset, color, opacity=None)
+
```
+
+
```python
+
grad = dw.LinearGradient(150, 0, 0, 0)
+
grad.add_stop(0, 'green')
+
grad.add_stop(1, 'yellow')
+
d.append(dw.Rectangle(10, 10, 150, 60,
+
stroke='black', fill=grad))
+
```
+
+
![svg](img/05_lingrad.svg)
+
+
+
### Radial Gradient
+
+
```python
+
gradient = dw.RadialGradient(cx, cy, r, **kwargs)
+
gradient.add_stop(offset, color, opacity=None)
+
```
+
+
```python
+
gradient = dw.RadialGradient(200, 100, 100)
+
gradient.add_stop(0, 'green', 1)
+
gradient.add_stop(1, 'orange', 1)
+
bg = dw.Rectangle(x=0, y=0, width='100%', height='100%', fill=gradient)
+
d.append(bg)
+
```
+
+
![svg](img/05_radgrad.svg)
+
+
+
### Clip
+
+
```python
+
clip_name = dw.ClipPath()
+
```
+
+
To add shape as Clip, use `.append()` method.
+
To apply Clip, fill `clip_path` argument with `clip_name`.
+
+
```python
+
# Show both shapes as they are
+
d.append(dw.Rectangle(100, 100, 100, 100,
+
stroke='gray', fill='none'))
+
d.append(dw.Circle(100, 100, 100,
+
fill='none', stroke='gray', stroke_dasharray='5 5'))
+
# Apply rect as clip to circle
+
clip = dw.ClipPath()
+
clip.append(dw.Rectangle(100, 100, 100, 100))
+
d.append(dw.Circle(100, 100, 100,
+
fill='cyan', clip_path=clip))
+
```
+
+
![svg](img/05_clip.svg)
+
+
Another example:
+
+
```python
+
# Draw a random path in the left half of the canvas
+
p = dw.Path(stroke='black', stroke_width=2, fill='none')
+
p.M(150, 150)
+
import random
+
random.seed(1)
+
for i in range(40):
+
p.L(random.randint(0, 300), random.randint(0, 200))
+
d.append(p)
+
+
# Circle as clipping shape
+
circ = dw.Circle(150, 100, 75)
+
c = dw.ClipPath()
+
c.append(circ)
+
+
# Repeat lines in the right half and apply clipping
+
d.append(dw.Use(p, 300, 0, clip_path=c))
+
```
+
+
![svg](img/05_clip2.svg)
+
+
Complex clip path:
+
+
```python
+
curve1 = dw.Path(stroke='black', stroke_width=1, stroke_dasharray='3 2', fill='none')
+
curve1.M(5, 55).C(25, 5, 45, -25, 75, 55).C(85, 85, 20, 105, 40, 55).Z()
+
+
curveClip = dw.ClipPath()
+
curveClip.append(dw.Use(curve1, 0, 0))
+
+
text1 = dw.Text('CLIP', 48, 20, 20, font_weight='bold', transform='rotate(60)',
+
stroke='black', stroke_width=1, stroke_dasharray='3 2', fill='none')
+
textClip = dw.ClipPath()
+
textClip.append(dw.Use(text1, 0, 0))
+
+
shapes = dw.Group()
+
shapes.append(dw.Rectangle(0, 50, 90, 60, fill='#999'))
+
shapes.append(dw.Circle(25, 25, 25, fill='#666'))
+
shapes.append(dw.Lines(30, 0, 80, 0, 80, 100, close='true', fill='#ccc'))
+
+
# draw shapes with clip path
+
d.append(dw.Use(shapes, 0, 0, clip_path=curveClip))
+
+
# show clip path
+
g = dw.Group(transform='translate(100,0)')
+
g.append(shapes)
+
g.append(dw.Use(curve1, 0, 0))
+
d.append(g)
+
+
# draw shapes with text as clip path
+
d.append(dw.Use(shapes, 0, 150, clip_path=textClip))
+
+
# show text clip path
+
g = dw.Group(transform='translate(100,150)')
+
g.append(shapes)
+
g.append(dw.Use(text1, 0, 0))
+
d.append(g)
+
```
+
+
![svg](img/05_clip3.svg)
+
+
+
### Mask
+
+
```python
+
mask_name = dw.Mask()
+
```
+
+
The transparency of the masking object is transfered to the masked object.
+
Opaque pixels of the mask produce opaque pixels of the masked object.
+
Transparent parts of the mask make the corresponding parts of the masked object invisible.
+
+
```python
+
gradient = dw.LinearGradient(*[0,0], *[1,0], gradientUnits='objectBoundingBox')
+
gradient.add_stop(0, 'white')
+
gradient.add_stop(1, 'black')
+
+
mask = dw.Mask()
+
box = dw.Rectangle(30, 0, 100, 100, fill=gradient)
+
mask.append(box)
+
+
# Initial shape
+
rect = dw.Rectangle(0, 0, 200, 100,
+
fill='cyan', stroke='blue', stroke_width=2)
+
d.append(rect)
+
+
# After mask
+
rect = dw.Rectangle(0, 0, 200, 100,
+
fill='pink', stroke='red', stroke_width=2,
+
mask=mask)
+
d.append(rect)
+
```
+
+
![svg](img/05_mask.svg)
+
+
Mask using opaque colors:
+
+
```python
+
# Define the masks
+
redmask = dw.Mask(maskContentUnits='objectBoundingBox')
+
redmask.append(dw.Rectangle(0, 0, 1, 1, fill='#f00'))
+
greenmask = dw.Mask(maskContentUnits='objectBoundingBox')
+
greenmask.append(dw.Rectangle(0, 0, 1, 1, fill='#0f0'))
+
bluemask = dw.Mask(maskContentUnits='objectBoundingBox')
+
bluemask.append(dw.Rectangle(0, 0, 1, 1, fill='#00f'))
+
whitemask = dw.Mask(maskContentUnits='objectBoundingBox')
+
whitemask.append(dw.Rectangle(0, 0, 1, 1, fill='#fff'))
+
+
# Display the colors
+
d.append(dw.Rectangle(10, 10, 50, 50, fill='#f00'))
+
d.append(dw.Rectangle(70, 10, 50, 50, fill='#0f0'))
+
d.append(dw.Rectangle(130, 10, 50, 50, fill='#00f'))
+
d.append(dw.Rectangle(190, 10, 50, 50, fill='#fff', stroke='black'))
+
+
# Mask
+
g = dw.Group(mask=redmask)
+
g.append(dw.Circle(35,115,25,fill='black'))
+
g.append(dw.Text('Red',14,35,80,text_anchor='middle'))
+
d.append(g)
+
g = dw.Group(mask=greenmask)
+
g.append(dw.Circle(95, 115, 25, fill='black'))
+
g.append(dw.Text('Green', 14, 95, 80, text_anchor='middle'))
+
d.append(g)
+
g = dw.Group(mask=bluemask)
+
g.append(dw.Circle(155, 115, 25, fill='black'))
+
g.append(dw.Text('Blue', 14, 155, 80, text_anchor='middle'))
+
d.append(g)
+
g = dw.Group(mask=whitemask)
+
g.append(dw.Circle(215, 115, 25, fill='black'))
+
g.append(dw.Text('White', 14, 215, 80, text_anchor='middle'))
+
d.append(g)
+
```
+
+
![svg](img/05_mask2.svg)
+
+
Mask alpha using opacity only:
+
+
```python
+
fullmask = dw.Mask(maskContentUnits='objectBoundingBox')
+
fullmask.append(dw.Rectangle(0, 0, 1, 1, fill_opacity=1.0, fill='white'))
+
three_fourths = dw.Mask(maskContentUnits='objectBoundingBox')
+
three_fourths.append(dw.Rectangle(0, 0, 1, 1, fill_opacity=0.75, fill='white'))
+
one_half = dw.Mask(maskContentUnits='objectBoundingBox')
+
one_half.append(dw.Rectangle(0, 0, 1, 1, fill_opacity=0.5, fill='white'))
+
one_fourth = dw.Mask(maskContentUnits='objectBoundingBox')
+
one_fourth.append(dw.Rectangle(0, 0, 1, 1, fill_opacity=0.25, fill='white'))
+
+
g = dw.Group(mask=fullmask)
+
g.append(dw.Circle(35, 35, 25))
+
g.append(dw.Text('100%', 14, 35, 80, text_anchor='middle'))
+
d.append(g)
+
g = dw.Group(mask=three_fourths)
+
g.append(dw.Circle(95, 35, 25))
+
g.append(dw.Text('50%', 14, 95, 80, text_anchor='middle'))
+
d.append(g)
+
g = dw.Group(mask=one_half)
+
g.append(dw.Circle(155, 35, 25))
+
g.append(dw.Text('50%', 14, 155, 80, text_anchor='middle'))
+
d.append(g)
+
g = dw.Group(mask=one_fourth)
+
g.append(dw.Circle(215, 35, 25))
+
g.append(dw.Text('25%', 14, 215, 80, text_anchor='middle'))
+
d.append(g)
+
```
+
+
![svg](img/05_mask3.svg)
+
+
+
## Group, Use, Defs, Image
+
+
### Group
+
+
```python
+
dw.Group(**kwargs)
+
```
+
+
Any style specified in the g tag will apply to all child elements in the group.
+
+
```python
+
g_house = dw.Group(id='house', fill='none', stroke='black')
+
g_house.append(dw.Rectangle(6, 50, 60, 60))
+
g_house.append(dw.Lines(6, 50, 36, 9, 66, 50))
+
g_house.append(dw.Lines(36, 110, 36, 80, 50, 80, 50, 110))
+
d.append(g_house)
+
+
g_man = dw.Group(id='man', fill='none', stroke='blue')
+
g_man.append(dw.Circle(85, 56, 10))
+
g_man.append(dw.Line(85, 66, 85, 80))
+
g_man.append(dw.Lines(76, 104, 85, 80, 94, 104))
+
g_man.append(dw.Lines(76, 70, 85, 76, 94, 70))
+
d.append(g_man)
+
+
g_woman = dw.Group(id='woman', fill='none', stroke='red')
+
g_woman.append(dw.Circle(110, 56, 10))
+
g_woman.append(dw.Lines(110, 66, 110, 80, 100, 90, 120, 90, 110, 80))
+
g_woman.append(dw.Line(104, 104, 108, 90))
+
g_woman.append(dw.Line(112, 90, 116, 104))
+
g_woman.append(dw.Lines(101, 70, 110, 76, 119, 70))
+
d.append(g_woman)
+
```
+
+
![svg](img/06_group.svg)
+
+
+
### Use
+
+
```python
+
dw.Use(other_elem, x, y, **kwargs)
+
```
+
+
```python
+
g_house = dw.Group(id='house', fill='none', stroke='black')
+
g_house.append(dw.Rectangle(6, 50, 60, 60))
+
g_house.append(dw.Lines(6, 50, 36, 9, 66, 50))
+
g_house.append(dw.Lines(36, 110, 36, 80, 50, 80, 50, 110))
+
d.append(g_house)
+
+
# Use id which is set
+
d.append(dw.Use('house', 100, 50))
+
# Or use variable name
+
d.append(dw.Use(g_house, 150, 20))
+
```
+
+
![svg](img/06_use.svg)
+
+
+
### Defs
+
+
Elements that are not appended to the drawing but are referenced by other elements will automatically be included in `<defs></defs>`.
+
([source](https://github.com/cduck/drawsvg/issues/46))
+
+
```python
+
d = dw.Drawing(200, 200, id_prefix='defs')
+
+
# Do not append `bond` to the drawing
+
bond = dw.Line(0, 0, 10, 10, stroke='black')
+
+
# `bond` is automatically added into <defs>
+
# A default `id` is generated if one isn't set
+
d.append(dw.Use(bond, 20, 50))
+
d.append(dw.Use(bond, 50, 50))
+
d.append(dw.Use(bond, 80, 50))
+
+
print(d.as_svg())
+
```
+
+
Output:
+
+
```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="200" height="200" viewBox="0 0 200 200">
+
<defs>
+
<path d="M0,0 L10,10" stroke="black" id="defs0" />
+
</defs>
+
<use xlink:href="#defs0" x="20" y="50" />
+
<use xlink:href="#defs0" x="50" y="50" />
+
<use xlink:href="#defs0" x="80" y="50" />
+
</svg>
+
```
+
+
+
### Image
+
+
```python
+
dw.Image(x, y, width, height, path=None, data=None,
+
embed=False, mimeType=None, **kwargs)
+
```
+
+
```python
+
d.append(dw.Image(0, 0, 200, 200, 'example1.png', embed=True))
+
```
+
+
![svg](img/06_imag.svg)
+
+
+
## Transformations
+
+
### Translate
+
+
```python
+
transform = 'translate(x,y)'
+
```
+
+
This attribute can be added to many objects. Simple example:
+
+
```python
+
d.append(dw.Rectangle(0, 0, 40, 40))
+
d.append(dw.Rectangle(0, 0, 40, 40, fill='red', transform='translate(50,50)'))
+
```
+
+
![svg](img/07_trans.svg)
+
+
+
### Scale
+
+
```python
+
transform = 'scale(x_mult[, y_mult])'
+
```
+
+
Note that scaling also affects stroke width.
+
+
```python
+
square = dw.Rectangle(0, 0, 40, 40, fill='none', stroke_width=2)
+
d.append(dw.Use(square, 10, 10, stroke='black'))
+
d.append(dw.Use(square, 10, 10, stroke='red', transform='scale(2)'))
+
```
+
+
![svg](img/07_scale.svg)
+
+
It is possible to specify x and y scale separately:
+
+
```python
+
square = dw.Rectangle(0, 0, 40, 40, fill='none', stroke_width=2)
+
d.append(dw.Use(square, 10, 10, stroke='black'))
+
d.append(dw.Use(square, 10, 10, stroke='red', transform='scale(3,1.5)'))
+
```
+
+
![svg](img/07_scale2.svg)
+
+
Scaling around a center point:
+
+
```python
+
# Center of scaling: (100, 100)
+
d.append(dw.Circle(100, 100, 4, fill='black'))
+
# Non-scaled rectangle
+
rect = dw.Rectangle(70, 80, 60, 40, stroke='black', fill='none')
+
d.append(rect)
+
# Scaled rectangles
+
d.append(dw.Use(rect, 0, 0, transform='translate(-100,-100) scale(2)', stroke_width=0.5))
+
d.append(dw.Use(rect, 0, 0, transform='translate(-150,-150) scale(2.5)', stroke_width=0.4))
+
d.append(dw.Use(rect, 0, 0, transform='translate(-200,-200) scale(3)', stroke_width=0.33))
+
```
+
+
![svg](img/07_scalcent.svg)
+
+
+
### Rotate
+
+
```python
+
transform = 'rotate(angle,cx=0,cy=0)'
+
```
+
+
`angle` counts clockwise in degrees.
+
`cx`/`cy` are the center of rotation.
+
+
```python
+
# Show frame border
+
d.append(dw.Rectangle(0, 0, 200, 200, stroke='gray', fill='none'))
+
# Rotation is around (0, 0)
+
d.append(dw.Rectangle(70, 30, 40, 40, fill='silver'))
+
d.append(dw.Rectangle(70, 30, 40, 40, fill='gray', transform='rotate(22.5)'))
+
d.append(dw.Rectangle(70, 30, 40, 40, fill='black', transform='rotate(45)'))
+
```
+
+
![svg](img/07_rota.svg)
+
+
```python
+
# Center of rotation
+
d.append(dw.Circle(100, 100, 3, fill='black'))
+
# Non-rotated arrow
+
arrow = dw.Group(id='arrow')
+
arrow.append(dw.Line(110, 100, 160, 100))
+
arrow.append(dw.Lines(160, 100, 155, 95, 155, 105))
+
d.append(dw.Use(arrow, 0, 0, stroke='black', fill='black'))
+
# Rotated arrows
+
g = dw.Group(stroke='red', fill='red')
+
g.append(dw.Use(arrow, 0, 0, transform='rotate (60,100,100)'))
+
g.append(dw.Use(arrow, 0, 0, transform='rotate (-90,100,100)'))
+
g.append(dw.Use(arrow, 0, 0, transform='rotate (-150,100,100)'))
+
d.append(g)
+
```
+
+
![svg](img/07_rota2.svg)
+
+
+
### Skew
+
+
```python
+
transform = 'skewX(angle)'
+
transform = 'skewY(angle)'
+
```
+
+
```python
+
g = dw.Group(stroke='gray', stroke_dasharray='4 4')
+
g.append(dw.Line(0, 0, 200, 0))
+
g.append(dw.Line(20, 0, 20, 90))
+
g.append(dw.Line(120, 0, 120, 90))
+
d.append(g)
+
+
h = dw.Group(transform='translate(20,0)')
+
h1 = dw.Group(transform='skewX(30)')
+
h1.append(dw.Lines(50, 0, 0, 0, 0, 50,
+
stroke='black', fill='none', stroke_width=2))
+
h1.append(dw.Text('skewX', 16, 0, 60))
+
h.append(h1)
+
d.append(h)
+
+
i = dw.Group(transform='translate(120,0)')
+
i1 = dw.Group(transform='skewY(30)')
+
i1.append(dw.Lines(50, 0, 0, 0, 0, 50,
+
stroke='black', fill='none', stroke_width=2))
+
i1.append(dw.Text('skewY', 16, 0, 60))
+
i.append(i1)
+
d.append(i)
+
```
+
+
![svg](img/07_skew.svg)
+
+
+
### Cartesian Coordinates
+
+
A drawing which can be translated to Cartesian coordinates
+
(where y-coordinates increase upward, not downward)
+
by setting the translate-y value to the drawing's height, and also applying `scale(1,-1)`.
+
+
Trapezoid with origin to top left (the default):
+
+
```python
+
d.append(dw.Lines(0, 100, 0, 0, 100, 0,
+
fill='none', stroke='green', stroke_width=2))
+
d.append(dw.Lines(40, 40, 100, 40, 70, 70, 40, 70,
+
fill='silver', stroke='black', stroke_width=2))
+
d.append(dw.Text('downward y', 12, 5, 95))
+
```
+
+
![svg](img/07_cart1.svg)
+
+
Translated origin to bottom left and upward-y:
+
+
```python
+
g = dw.Group(transform='translate(0,100) scale(1,-1)')
+
g.append(dw.Lines(0, 100, 0, 0, 100, 0,
+
fill='none', stroke='green', stroke_width=2))
+
g.append(dw.Lines(40, 40, 100, 40, 70, 70, 40, 70,
+
fill='silver', stroke='black', stroke_width=2))
+
g.append(dw.Text('upward y', 12, 5, 95))
+
d.append(g)
+
```
+
+
![svg](img/07_cart2.svg)
+
+
Alternatively, apply `scale(1,-1)` to the whole drawing:
+
+
```python
+
d = dw.Drawing(100, 100, id_prefix='cart3', transform='scale(1,-1)')
+
d.append(dw.Lines(0, 100, 0, 0, 100, 0,
+
fill='none', stroke='green', stroke_width=2))
+
d.append(dw.Lines(40, 40, 100, 40, 70, 70, 40, 70,
+
fill='silver', stroke='black', stroke_width=2))
+
d.append(dw.Text('upward y', 12, 5, 95))
+
```
+
+
![svg](img/07_cart3.svg)
+
+
+
## Credits
+
+
Written by joachim heintz 2023. Edited by Casey Duckering.
+
+
Most examples are based on J. David Eisenberg, SVG Essentials, O'Reilly 2002.
+
+
Thanks to [Ahmad Aufar Husaini](https://github.com/aufarah) for his fork (draw2Svg) and for providing some documentation [here](https://draw2svg.netlify.app/) (some examples are used in this Quick Reference).
+
+
Thanks to [Casey Duckering](https://github.com/cduck) for drawsvg and many helpful explanations on its [discussion page](https://github.com/cduck/drawsvg/discussions).
+1
drawsvg/__init__.py
···
FrameAnimation,
frame_animate_video,
frame_animate_jupyter,
+
frame_animate_spritesheet,
)
from .native_animation import (
SyncedAnimationConfig,
+42 -5
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,
)
XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>\n'
SVG_START = ('<svg xmlns="http://www.w3.org/2000/svg" '
-
'xmlns:xlink="http://www.w3.org/1999/xlink"\n ')
+
'xmlns:xlink="http://www.w3.org/1999/xlink"\n ')
SVG_END = '</svg>'
SVG_CSS_FMT = '<style>/*<![CDATA[*/{}/*]]>*/</style>'
SVG_JS_FMT = '<script>/*<![CDATA[*/{}/*]]>*/</script>'
···
else:
self.elements.extend(iterable)
def insert(self, i, element):
+
'''Inserts a top-level element at the given array index.'''
self.elements.insert(i, element)
def remove(self, element):
+
'''Removes a top-level element (except those with a z-index).'''
self.elements.remove(element)
def clear(self):
+
'''Clears all drawing elements, with or without a z-index, but keeps
+
defs-type elements added with `append_def()`.
+
'''
self.elements.clear()
+
self.ordered_elements.clear()
def index(self, *args, **kwargs):
+
'''Finds the array-index of a top-level element (except those with a
+
z-index).
+
'''
return self.elements.index(*args, **kwargs)
def count(self, element):
+
'''Counts the number of top-level elements (except those with a z-index
+
).
+
'''
return self.elements.count(element)
def reverse(self):
+
'''Reverses the order of all elements (except those with a z-index).'''
self.elements.reverse()
def draw_def(self, obj, **kwargs):
if not hasattr(obj, 'write_svg_element'):
···
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'):
···
id_index += 1
return id_str
id_map = defaultdict(id_gen)
-
prev_set = set((id(defn) for defn in self.other_defs))
-
prev_list = []
+
prev_set = set()
def is_duplicate(obj):
nonlocal prev_set
dup = id(obj) in prev_set
prev_set.add(id(obj))
-
prev_list.append(obj)
return dup
for element in self.other_defs:
if hasattr(element, 'write_svg_element'):
···
self.as_mp4(
fname, fps=fps, duration=duration, context=context,
verbose=verbose)
+
def save_spritesheet(self, fname, fps=10, duration=None, context=None,
+
row_length=None, verbose=False):
+
self.as_spritesheet(
+
fname, fps=fps, duration=duration, context=context,
+
row_length=row_length, verbose=verbose)
def as_video(self, to_file=None, fps=10, duration=None,
mime_type=None, file_type=None, context=None, verbose=False):
if file_type is None and mime_type is None:
···
return self.as_video(
to_file=to_file, fps=fps, duration=duration, context=context,
mime_type='video/mp4', file_type='mp4', verbose=verbose)
+
def as_spritesheet(self, to_file=None, fps=10, duration=None, context=None,
+
row_length=None, verbose=False):
+
frames = self.as_animation_frames(
+
fps=fps, duration=duration, context=context)
+
sheet = video.render_spritesheet(
+
frames, row_length=row_length, verbose=verbose)
+
return raster.Raster.from_arr(sheet, out_file=to_file)
def _repr_svg_(self):
'''Display in Jupyter notebook.'''
return self.as_svg(randomize_ids=True)
+25 -1
drawsvg/elements.py
···
'''
TAG_NAME = 'text'
has_content = True
-
def __new__(cls, text, *args, path=None, id=None, _skip_check=False,
+
def __new__(cls, text='', *args, path=None, id=None, _skip_check=False,
**kwargs):
# Check for the special case of multi-line text on a path
# This is inconsistently implemented by renderers so we return a group
···
def __init__(self, cx, cy, r, start_deg, end_deg, cw=False, **kwargs):
super().__init__(d='', **kwargs)
self.arc(cx, cy, r, start_deg, end_deg, cw=cw, include_m=True)
+
+
class ForeignObject(DrawingBasicElement):
+
'''A foreign object, i.e. for embedding HTML.
+
+
In the context of SVG embedded in an HTML document, the XHTML namespace
+
could be omitted in the foreignObject content, but it is mandatory in the
+
context of an SVG document.
+
+
This element works best when viewing the SVG in a browser. Drawing.rasterize()
+
and Drawing.save_png() are unable to display this element.
+
+
Additional keyword arguments are output as additional arguments to the SVG
+
node e.g. fill="red", stroke="#ff4477", stroke_width=2.
+
'''
+
TAG_NAME = 'foreignObject'
+
has_content = True
+
def __init__(self, content, **kwargs):
+
super().__init__(**kwargs)
+
self.content = content
+
def write_content(self, id_map, is_duplicate, output_file, lcontext,
+
dry_run):
+
if dry_run:
+
return
+
output_file.write(self.content)
+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)
+31 -5
drawsvg/frame_animation.py
···
def save_video(self, file, **kwargs):
video.save_video(self.frames, file, **kwargs)
+
def save_spritesheet(self, file, **kwargs):
+
video.save_spritesheet(self.frames, file, **kwargs)
+
class FrameAnimationContext:
def __init__(self, draw_func=None, out_file=None,
-
jupyter=False, pause=False, clear=True, delay=0, disable=False,
-
video_args=None, _patch_delay=0.05):
+
jupyter=False, spritesheet=False, pause=False,
+
clear=True, delay=0, disable=False, video_args=None,
+
_patch_delay=0.05):
self.jupyter = jupyter
+
self.spritesheet = spritesheet
self.disable = disable
if self.jupyter and not self.disable:
from IPython import display
···
if exc_value is None:
# No error
if self.out_file is not None and not self.disable:
-
self.anim.save_video(self.out_file, **self.video_args)
+
if self.spritesheet:
+
self.anim.save_spritesheet(self.out_file, **self.video_args)
+
else:
+
self.anim.save_video(self.out_file, **self.video_args)
def frame_animate_video(out_file, draw_func=None, jupyter=False, **video_args):
···
Example:
```
-
with animate_video('video.mp4') as anim:
+
with frame_animate_video('video.mp4') as anim:
while True:
...
anim.draw_frame(...)
···
return FrameAnimationContext(draw_func=draw_func, out_file=out_file,
jupyter=jupyter, video_args=video_args)
+
def frame_animate_spritesheet(out_file, draw_func=None, jupyter=False,
+
**video_args):
+
'''
+
Returns a context manager that stores frames and saves a spritesheet when
+
the context exits.
+
+
Example:
+
```
+
with frame_animate_spritesheet('sheet.png', row_length=10) as anim:
+
while True:
+
...
+
anim.draw_frame(...)
+
```
+
'''
+
return FrameAnimationContext(draw_func=draw_func, out_file=out_file,
+
jupyter=jupyter, spritesheet=True,
+
video_args=video_args)
+
def frame_animate_jupyter(draw_func=None, pause=False, clear=True, delay=0.1,
**kwargs):
···
Example:
```
-
with animate_jupyter(delay=0.5) as anim:
+
with frame_animate_jupyter(delay=0.5) as anim:
while True:
...
anim.draw_frame(...)
+24
drawsvg/raster.py
···
) from e
return cairosvg
+
def delay_import_imageio():
+
try:
+
import imageio
+
except ImportError as e:
+
raise ImportError(
+
'Optional dependencies not installed. '
+
'Install with `python3 -m pip install "drawsvg[all]"` '
+
'or `python3 -m pip install "drawsvg[raster]"`. '
+
'See https://github.com/cduck/drawsvg#full-feature-install '
+
'for more details.'
+
) from e
+
return imageio
+
class Raster:
def __init__(self, png_data=None, png_file=None):
···
cairosvg = delay_import_cairo()
cairosvg.svg2png(bytestring=svg_data, write_to=out_file)
return Raster(None, png_file=out_file)
+
@staticmethod
+
def from_arr(arr, out_file=None):
+
imageio = delay_import_imageio()
+
if out_file is None:
+
with io.BytesIO() as f:
+
imageio.imwrite(f, arr, format='png')
+
f.seek(0)
+
return Raster(f.read())
+
else:
+
imageio.imwrite(out_file, arr, format='png')
+
return Raster(None, png_file=out_file)
def _repr_png_(self):
if self.png_data:
return self.png_data
+69
drawsvg/video.py
···
print()
print(f'Converting to video')
imageio.mimsave(file, frames, **kwargs)
+
+
def render_spritesheet(frames, row_length=None, verbose=False, **kwargs):
+
'''
+
Save a series of drawings as a bitmap spritesheet
+
+
Arguments:
+
frames: A list of `Drawing`s or a list of `numpy.array`s.
+
row_length: The length (in frames) of one row in the spritesheet.
+
If not provided, all frames go on one row.
+
align_bottom: If frames are different sizes, align the bottoms of each
+
frame in the video.
+
align_right: If frames are different sizes, align the right edge of each
+
frame in the video.
+
bg: If frames are different sizes, fill the background with this color.
+
(default is white: (255, 255, 255, 255))
+
**kwargs: Other arguments to imageio.imsave().
+
+
'''
+
np, _ = delay_import_np_imageio()
+
if not isinstance(frames[0], np.ndarray):
+
frames = render_svg_frames(frames, verbose=verbose, **kwargs)
+
kwargs.pop('align_bottom', None)
+
kwargs.pop('align_right', None)
+
bg = kwargs.pop('bg', (255, 255, 255, 255))
+
+
cols = row_length if row_length is not None else len(frames)
+
rows = (len(frames) - 1) // cols + 1
+
+
if rows * cols > len(frames): # Unfilled final row
+
empty_frame = np.zeros(frames[0].shape, dtype=frames[0].dtype)
+
empty_frame[..., :] = bg[:empty_frame.shape[-1]]
+
frames.extend([empty_frame] * (rows * cols - len(frames)))
+
+
block_arrangement = []
+
for row in range(rows):
+
next_row_end = (row+1)*cols
+
block_arrangement.append([
+
[frame] for frame in frames[row*cols:next_row_end]
+
])
+
+
spritesheet = np.block(block_arrangement)
+
return spritesheet
+
+
def save_spritesheet(frames, file, row_length=None, verbose=False, **kwargs):
+
'''
+
Save a series of drawings as a bitmap spritesheet
+
+
Arguments:
+
frames: A list of `Drawing`s or a list of `numpy.array`s.
+
file: File name or file like object to write the spritesheet to. The
+
extension determines the output format.
+
row_length: The length (in frames) of one row in the spritesheet.
+
If not provided, all frames go on one row.
+
align_bottom: If frames are different sizes, align the bottoms of each
+
frame in the video.
+
align_right: If frames are different sizes, align the right edge of each
+
frame in the video.
+
bg: If frames are different sizes, fill the background with this color.
+
(default is white: (255, 255, 255, 255))
+
**kwargs: Other arguments to imageio.imsave().
+
+
'''
+
_, imageio = delay_import_np_imageio()
+
spritesheet = render_spritesheet(
+
frames, row_length=row_length, verbose=verbose, **kwargs)
+
kwargs.pop('align_bottom', None)
+
kwargs.pop('align_right', None)
+
kwargs.pop('bg', None)
+
imageio.imsave(file, spritesheet, **kwargs)
+11
drawsvg/widgets/drawing_javascript.py
···
var svg_pt = this.cursor_point.matrixTransform(
this.svg_view.getScreenCTM().inverse());
+
var target_parents = [];
+
var target = e.target;
+
while(target && target != this.svg_view)
+
{
+
if (target.id) {
+
target_parents.push(target.id);
+
}
+
target = target.parentNode;
+
}
+
this.send({
name: name,
x: svg_pt.x,
···
movementY: e.movementY,
timeStamp: e.timeStamp,
targetId: e.target ? e.target.id : null,
+
targetParentIds: target_parents,
currentTargetId: e.currentTarget ? e.currentTarget.id : null,
relatedTargetId: e.relatedTarget ? e.relatedTarget.id : null,
});
examples/example6.png

This is a binary file and will not be displayed.

+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>
examples/orbit-spritesheet.png

This is a binary file and will not be displayed.

+4
mkdocs.yml
···
+
site_name: Drawsvg Quick Reference
+
nav:
+
- Home: index.md
+
theme: readthedocs
+1 -1
setup.py
···
import logging
logger = logging.getLogger(__name__)
-
version = '2.0.2'
+
version = '2.4.0'
try:
with open('README.md', 'r') as f: