All formats

canvas

LanguageGo
LicenseMIT
Repositorygithub.com/tdewolff/canvas
Documentationpkg.go.dev, wiki
Depends onpure Go (tdewolff/font, golang.org/x/image); optional CGO HarfBuzz/FriBidi
Read / writeyes (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#

Shell
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#

Go
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:

Go
renderers.Write("out@2x.png", c, canvas.DPMM(2))

How to draw shapes and save them as SVG#

Go
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:

Go
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#