canvas
| Language | Go |
|---|---|
| License | MIT |
| Repository | github.com/tdewolff/canvas |
| Documentation | pkg.go.dev, wiki |
| Depends on | pure Go (tdewolff/font, golang.org/x/image); optional CGO HarfBuzz/FriBidi |
| Read / write | yes (ParseSVG) / yes (SVG, SVGZ, plus PDF, EPS, PNG, JPEG, TIFF, WebP, AVIF) |
canvas is a 2D vector drawing library, comparable to Cairo, with precise path operations (stroking, dashing, boolean operations), text layout with font embedding, and many output formats. Draw once and write the result as SVG, PDF, EPS or a raster image. canvas.ParseSVG loads an SVG file into a canvas, which makes it a pure Go SVG to PDF/PNG converter.
The SVG parser covers common static SVG (shapes, paths, transforms, fills, strokes, text) but not everything a browser supports: test it with your files. Units are millimeters, and the y axis points up (origin at the bottom-left), unlike SVG.
Installation#
go get github.com/tdewolff/canvas@latest
The module has no tagged releases; @latest picks the newest commit.
Basic usage: convert SVG to PNG and PDF#
package main
import (
"fmt"
"log"
"os"
"github.com/tdewolff/canvas"
"github.com/tdewolff/canvas/renderers"
)
func main() {
f, err := os.Open("in.svg")
if err != nil {
log.Fatal(err)
}
defer f.Close()
c, err := canvas.ParseSVG(f)
if err != nil {
log.Fatal(err)
}
w, h := c.Size()
fmt.Printf("%.0f x %.0f\n", w, h)
// ParseSVG maps one SVG user unit to 1 mm, so DPMM(1) gives one pixel per SVG unit
if err := renderers.Write("out.png", c, canvas.DPMM(1)); err != nil {
log.Fatal(err)
}
if err := renderers.Write("out.pdf", c); err != nil {
log.Fatal(err)
}
}
renderers.Write picks the format from the file extension: .png, .jpg, .gif, .tiff, .bmp, .webp, .avif, .svg, .svgz, .pdf, .ps, .eps, .tex.
How to render SVG at a higher resolution#
Pass a larger resolution; canvas.DPMM(2) doubles the pixel size:
renderers.Write("out@2x.png", c, canvas.DPMM(2))
How to draw shapes and save them as SVG#
package main
import (
"log"
"github.com/tdewolff/canvas"
"github.com/tdewolff/canvas/renderers"
)
func main() {
c := canvas.New(100, 50) // width, height in mm
ctx := canvas.NewContext(c)
ctx.SetFillColor(canvas.Steelblue)
ctx.DrawPath(5, 5, canvas.RoundedRectangle(30, 30, 4)) // x, y of the bottom-left corner
ctx.SetFillColor(canvas.Orange)
ctx.DrawPath(50, 25, canvas.Circle(20))
triangle, err := canvas.ParseSVGPath("M0 0L20 0L10 15z")
if err != nil {
log.Fatal(err)
}
ctx.SetFillColor(canvas.Black)
ctx.DrawPath(70, 10, triangle)
if err := renderers.Write("drawing.svg", c); err != nil {
log.Fatal(err)
}
if err := renderers.Write("drawing.svgz", c); err != nil { // gzip-compressed
log.Fatal(err)
}
}
How to use SVG path data#
canvas.ParseSVGPath turns a d attribute into a *canvas.Path that you can transform, stroke, dash or combine with boolean operations:
p, err := canvas.ParseSVGPath("M10 10 h80 v80 h-80 Z")
if err != nil {
log.Fatal(err)
}
outline := p.Stroke(2, canvas.RoundCap, canvas.RoundJoin, 0.01)
fmt.Println(outline.ToSVG()) // back to SVG path data
Tips#
- Load fonts for text with
canvas.NewFontFamily(name)and thenLoadSystemFontorLoadFontFile. - Pass
canvas.SRGBColorSpace{}as a rasterizer option for gamma-correct blending; the default blends like browsers and PDF viewers do.