# Cairo in Go: vector to SVG, PDF, EPS, raster, HTML Canvas, etc.

### Related tags

Images tools canvas
##### Overview

Canvas is a common vector drawing target that can output SVG, PDF, EPS, raster images (PNG, JPG, GIF, ...), HTML Canvas through WASM, and OpenGL. It has a wide range of path manipulation functionality such as flattening, stroking and dashing implemented. Additionally, it has a good text formatter and embeds fonts (TTF, OTF, WOFF, or WOFF2) or converts them to outlines. It can be considered a Cairo or node-canvas alternative in Go. See the example below in Fig. 1 and Fig. 2 for an overview of the functionality.

Figure 1: top-left you can see text being fitted into a box and their bounding box (orange-red), the spaces between the words on the first row are being stretched to fill the whole width. You can see all the possible styles and text decorations applied. Also note the typographic substitutions (the quotes) and ligature support (fi, ffi, ffl, ...). Below the text box, the word "stroke" is being stroked and drawn as a path. Top-right we see a LaTeX formula that has been converted to a path. Left of that we see ellipse support showcasing precise dashing, notably the length of e.g. the short dash is equal wherever it is (approximated through arc length parametrization) on the curve. It also shows support for alternating dash lengths, in this case (2.0, 4.0, 2.0) for dashes and for spaces. Note that the dashes themselves are elliptical arcs as well (thus exactly precise even if magnified greatly). In the bottom-right we see a closed polygon of four points being smoothed by cubic Béziers that are smooth along the whole path, and next to it on the left an open path. In the middle you can see a rasterized image painted.

Figure 2abc: Three examples of what is possible with this library, for example the plotting of graphs, maps and documents.

Live WASM HTML Canvas example

Terminology: a path is a sequence of drawing commands (MoveTo, LineTo, QuadTo, CubeTo, ArcTo, Close) that completely describe a path. QuadTo and CubeTo are quadratic and cubic Béziers respectively, ArcTo is an elliptical arc, and Close is a LineTo to the last MoveTo command and closes the path (sometimes this has a special meaning such as when stroking). A path can consist of several subpaths by having more than one MoveTo or Close command. A subpath consists of path segments which are defined by a command and some values or coordinates.

Flattening is the act of converting the QuadTo, CubeTo and ArcTo segments into LineTos so that all path segments are linear.

### Getting Started

With modules enabled, add the following imports and run the project with go get

import (
"github.com/tdewolff/canvas"
)

#### Examples

Preview: canvas preview (as shown above) showing most of the functionality and exporting as PNG, SVG, PDF and EPS. It shows image and text rendering as well as LaTeX support and path functionality.

Map: data is loaded from Open Street Map of the city centre of Amsterdam and rendered to a PNG.

Graph: a simple graph is being plotted using the CO2 data from the Mauna Loa observatory.

Text document: a simple text document is rendered to PNG.

HTML Canvas: using WASM, a HTML Canvas is used as target. Live demo.

TeX/PGF: using the PGF (TikZ) LaTeX package, the output can be directly included in the main TeX file.

OpenGL: rendering example to an OpenGL target (WIP).

go-chart: using the go-chart library a financial graph is plotted.

gonum/plot: using the gonum/plot library an example is plotted.

My own

Papers

## Status

### Targets

Feature Image SVG PDF EPS WASM Canvas OpenGL
Draw path fill yes yes yes yes yes no
Draw path stroke yes yes yes no yes no
Draw path dash yes yes yes no yes no
Embed fonts yes yes no no no
Draw text path yes yes path path path
Draw image yes yes yes no yes no
EvenOdd fill rule no yes yes no no no
• EPS does not support transparency
• PDF and EPS do not support line joins for last and first dash for closed dashed path
• OpenGL proper tessellation is missing

### Path

Command Flatten Stroke Length SplitAt
LineTo yes yes yes yes
QuadTo yes (CubeTo) yes (CubeTo) yes yes (GL5 + Chebyshev10)
CubeTo yes yes yes (GL5) yes (GL5 + Chebyshev10)
ArcTo yes yes yes (GL5) yes (GL5 + Chebyshev10)
• Ellipse => Cubic Bézier: used by rasterizer and PDF targets (see Maisonobe paper)

NB: GL5 means a Gauss-Legendre n=5, which is an numerical approximation as there is no analytical solution. Chebyshev is a converging way to approximate a function by an n=10 degree polynomial. It uses the bisection method as well to determine the polynomial points.

## Planning

Features that are planned to be implemented in the future, with important issues in bold. Also see the TODOs in the code.

General

• Fix slowness in the rasterizer (text_example.go is slow! use rasterized cache for each glyph/path)
• Use general span placement algorithm (like CSS flexbox) that replace the current Text placer, to allow for text, image, path elements (e.g. inline formulas, inline icons or emoticons, ...)
• Use word breaking algorithm from Knuth & Plass, implemented in JS in typeset. Use letter stretching and shrinking, shrinking by using ligatures, space shrinking and stretching (depending if space is between words or after comma or dot), and spacing or shrinking between glyphs. Use a point system of how ugly breaks are on a paragraph basis. Also see Justify Just or Just Justify.
• Load in Markdown/HTML formatting and turn into text
• Add OpenGL target, needs tessellation (see Delaunay triangulation). See Resolution independent NURBS curves rendering using programmable graphics pipeline and poly2tri-go. Use rational quadratic Beziérs to represent quadratic Beziérs and elliptic arcs exactly, and reduce degree of cubic Beziérs. Using a fragment shader we can draw all curves exactly. Or use rational cubic Beziérs to represent them all exactly?

Fonts

• Compressing fonts and embedding only used characters
• Use ligature and OS/2 tables
• Support EOT font format
• Font embedding for EPS
• Support font hinting (for the rasterizer)?

Paths

• Avoid overlapping paths when offsetting in corners
• Get position and derivative/normal at length L along the path
• Simplify polygons using the Ramer-Douglas-Peucker algorithm
• Intersection function between line, Bézier and ellipse and between themselves (for path merge, overlap/mask, clipping, etc.)
• Implement Bentley-Ottmann algorithm to find all line intersections (clipping)

Far future

• Support fill gradients and patterns (hard)
• Load in PDF, SVG and EPS and turn to paths/text
• Generate TeX-like formulas in pure Go, use OpenType math font such as STIX or TeX Gyre

## Canvas

c := canvas.New(width, height float64)

ctx := canvas.NewContext(c)
ctx.Push()               // save state set by function below on the stack
ctx.Pop()                // pop state from the stack
ctx.SetView(Matrix)      // set view transformation, all drawn elements are transformed by this matrix
ctx.ComposeView(Matrix)  // add transformation after the current view transformation
ctx.ResetView()          // use identity transformation matrix
ctx.SetFillColor(color.Color)
ctx.SetStrokeColor(color.Color)
ctx.SetStrokeCapper(Capper)
ctx.SetStrokeJoiner(Joiner)
ctx.SetStrokeWidth(width float64)
ctx.SetDashes(offset float64, lengths ...float64)

ctx.DrawPath(x, y float64, *Path)
ctx.DrawText(x, y float64, *Text)
ctx.DrawImage(x, y float64, image.Image, dpm float64)

c.Fit(margin float64)  // resize canvas to fit all elements with a given margin

c.WriteFile(filename string, svg.Writer)
c.WriteFile(filename string, pdf.Writer)
c.WriteFile(filename string, eps.Writer)
c.WriteFile(filename string, rasterizer.PNGWriter(resolution DPMM))
c.WriteFile(filename string, rasterizer.JPGWriter(resolution DPMM, opts *jpeg.Options))
c.WriteFile(filename string, rasterizer.GIFWriter(resolution DPMM, opts *gif.Options))
rasterizer.Draw(c *Canvas, resolution DPMM) *image.RGBA

Canvas allows to draw either paths, text or images. All positions and sizes are given in millimeters.

## Text

dejaVuSerif := NewFontFamily("dejavu-serif")
err := dejaVuSerif.LoadFontFile("DejaVuSerif.ttf", canvas.FontRegular)  // TTF, OTF, WOFF, or WOFF2
ff := dejaVuSerif.Face(size float64, color.Color, FontStyle, FontVariant, ...FontDecorator)

text = NewTextLine(ff, "string\nsecond line", halign) // simple text line
text = NewTextBox(ff, "string", width, height, halign, valign, indent, lineStretch)  // split on word boundaries and specify text alignment

// rich text allowing different styles of text in one box
richText := NewRichText()  // allow different FontFaces in the same text block
text = richText.ToText(width, height, halign, valign, indent, lineStretch)

ctx.DrawText(0.0, 0.0, text)

Note that the LoadLocalFont function will use fc-match "font name" to find the closest matching font.

## Paths

A large deal of this library implements functionality for building paths. Any path can be constructed from a few basic commands, see below. Successive commands build up segments that start from the current pen position (which is the previous segments's end point) and are drawn towards a new end point. A path can consist of multiple subpaths which each start with a MoveTo command (there is an implicit MoveTo after each Close command), but be aware that overlapping paths can cancel each other depending on the FillRule.

p := &Path{}
p.MoveTo(x, y float64)                                            // new subpath starting at (x,y)
p.LineTo(x, y float64)                                            // straight line to (x,y)
p.QuadTo(cpx, cpy, x, y float64)                                  // a quadratic Bézier with control point (cpx,cpy) and end point (x,y)
p.CubeTo(cp1x, cp1y, cp2x, cp2y, x, y float64)                    // a cubic Bézier with control points (cp1x,cp1y), (cp2x,cp2y) and end point (x,y)
p.ArcTo(rx, ry, rot float64, largeArc, sweep bool, x, y float64)  // an arc of an ellipse with radii (rx,ry), rotated by rot (in degrees CCW), with flags largeArc and sweep (booleans, see https://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands)
p.Arc(rx, ry, rot float64, theta0, theta1 float64)                // an arc of an ellipse with radii (rx,ry), rotated by rot (in degrees CCW), beginning at angle theta0 and ending at angle theta1
p.Close()                                                         // close the path, essentially a LineTo to the last MoveTo location

p = Rectangle(w, h float64)
p = RoundedRectangle(w, h, r float64)
p = BeveledRectangle(w, h, r float64)
p = Circle(r float64)
p = Ellipse(rx, ry float64)
p = RegularPolygon(n int, r float64, up bool)
p = RegularStarPolygon(n, d int, r float64, up bool)
p = StarPolygon(n int, R, r float64, up bool)

We can extract information from these paths using:

p.Empty() bool                 // true if path contains no segments (ie. no commands other than MoveTo or Close)
p.Pos() (x, y float64)         // current pen position
p.StartPos() (x, y float64)    // position of last MoveTo
p.Coords() []Point             // start/end positions of all segments
p.CCW() bool                   // true if the path is (mostly) counter clockwise
p.Interior(x, y float64) bool  // true if (x,y) is in the interior of the path, ie. gets filled (depends on FillRule)
p.Filling() []bool             // for all subpaths, true if the subpath is filling (depends on FillRule)
p.Bounds() Rect                // bounding box of path
p.Length() float64             // length of path in millimeters

These paths can be manipulated and transformed with the following commands. Each will return a pointer to the path.

p = p.Copy()
p = p.Append(q *Path)                 // append path q to p and return a new path
p = p.Join(q *Path)                   // join path q to p and return a new path
p = p.Reverse()                       // reverse the direction of the path
ps = p.Split() []*Path                // split the subpaths, ie. at Close/MoveTo
ps = p.SplitAt(d ...float64) []*Path  // split the path at certain lengths d

p = p.Transform(Matrix)               // apply multiple transformations at once and return a new path
p = p.Translate(x, y float64)

p = p.Flatten()                                            // flatten Bézier and arc segments to straight lines
p = p.Offset(width float64)                                // offset the path outwards (width > 0) or inwards (width < 0), depends on FillRule
p = p.Stroke(width float64, capper Capper, joiner Joiner)  // create a stroke from a path of certain width, using capper and joiner for caps and joins
p = p.Dash(offset float64, d ...float64)                   // create dashed path with lengths d which are alternating the dash and the space, start at an offset into the given pattern (can be negative)

### Polylines

Some operations on paths only work when it consists of linear segments only. We can either flatten an existing path or use the start/end coordinates of the segments to create a polyline.

polyline := PolylineFromPath(p)       // create by flattening p
polyline = PolylineFromPathCoords(p)  // create from the start/end coordinates of the segments of p

polyline.Smoothen()              // smoothen it by cubic Béziers
polyline.FillCount() int         // returns the fill count as dictated by the FillRule
polyline.Interior(x, y float64)  // returns true if (x,y) is in the interior of the polyline

### Path stroke

Below is an illustration of the different types of Cappers and Joiners you can use when creating a stroke of a path:

## LaTeX

To generate outlines generated by LaTeX, you need latex and dvisvgm installed on your system.

p, err := ParseLaTeX($y=\sin$$\frac{x}{180}\pi$$$)
if err != nil {
panic(err)
}

Where the provided string gets inserted into the following document template:

\documentclass{article}
\begin{document}
\thispagestyle{empty}
{{input}}
\end{document}

### Examples

See https://github.com/tdewolff/canvas/tree/master/examples for a working examples.

• #### drawImage has size error after image.bounds was modified

I'm adding crop feature now, and found a strange case.

At first, here is my code, I used module "github.com/oliamb/cutter" @see the doc here, if you need

croppedImg, _ := cutter.Crop(img, cutter.Config{
Width:  i.Clip.Width,
Height: i.Clip.Height,
Anchor: image.Point{i.Clip.X, i.Clip.Y},
})
img = croppedImg

// omitted some code, it doesn't matter
c.DrawImage(x, y, img, scale)


The image size was 430 * 430, I crop it, from startPos(30, 30) to (400, 400). after I doing this, img.Bounds() will lead to (30,30)-(400,400). this could be reason about the case.

So, I save the image to local. here is anthoer test,if I do this. the img.bounds() will be (0,0)-(370,370), and result is fine.

croppedImg, _ := cutter.Crop(img, cutter.Config{
Width:  i.Clip.Width,
Height: i.Clip.Height,
Anchor: image.Point{i.Clip.X, i.Clip.Y},
})
img = croppedImg

f, err := os.Create("test_crop.jpg")
if err != nil {
panic(err)
}
defer f.Close()
jpeg.Encode(f, img, nil)

// omitted some code, it doesn't matter
c.DrawImage(x, y, img, scale)


I've tried find reason about this, include try another crop image service from aliyun. but it shows similarity.

Here is some code from module "github.com/oliamb/cutter", I thinks it's normal implementation.

func Crop(img image.Image, c Config) (image.Image, error) {
maxBounds := c.maxBounds(img.Bounds())
size := c.computeSize(maxBounds, image.Point{c.Width, c.Height})
cr := c.computedCropArea(img.Bounds(), size)
cr = img.Bounds().Intersect(cr)

if c.Options&Copy == Copy {
return cropWithCopy(img, cr)
}
if dImg, ok := img.(subImageSupported); ok {
return dImg.SubImage(cr), nil
}
return cropWithCopy(img, cr)
}


Thanks.

opened by luankefei 16
• #### Not drawing some text

I am flabbergasted. I stripped down a problem to the following.

This works:

  ctx.DrawText(lx, ly, canvas.NewTextLine(face, fmt.Sprintf(
"XX",
), 0))


This doesn't draw anything

  ctx.DrawText(lx, ly, canvas.NewTextLine(face, fmt.Sprintf(
"XF",
), 0))


Any idea?

opened by tcurdt 16
• #### fc-match for mac ?

i have a need for supporting outputting in many formats and so this code base looks like the "dogs bollocks" as they say, but when trying the examples on a mac, i kept on hitting the need to have fc-match.

i am on mac. I tried both branches...



ex-run-doc:
# panic: exec: "fc-match": executable file not found in $PATH cd$(EX)/document && go run .

ex-run-gio:
# ! panic: exec: "fc-match": executable file not found in $PATH cd$(EX)/gio && $(MAKE) all ex-run-chart: # :) cd$(EX)/go-chart && go run .

ex-run-opengl:
cd $(EX)/opengl &&$(MAKE) all

ex-run-map:
# ! needs "fc-match" exe

368 Jun 25, 2022
###### Super fast static photo and video gallery generator (written in Go and HTML/CSS/native JS)

fastgallery Fast static photo and video gallery generator Super fast (written in Go and C, concurrent, uses fastest image/video libraries, 4-8 times f

24 May 25, 2022
###### Imgpreview - Tiny image previews for HTML while the original image is loading

imgpreview This is a Go program that generates tiny blurry previews for images t

8 May 22, 2022
###### 2D rendering for different output (raster, pdf, svg)

draw2d Package draw2d is a go 2D vector graphics library with support for multiple outputs such as images (draw2d), pdf documents (draw2dpdf), opengl

918 Jun 28, 2022
###### Canvas is a Go drawing library based on OpenGL or using software rendering that is very similar to the HTML5 canvas API

Go canvas Canvas is a pure Go library that provides drawing functionality as similar as possible to the HTML5 canvas API. It has nothing to do with HT

426 Jun 19, 2022
###### Rasterx is an SVG 2.0 path compliant rasterizer that can use either the golang vector or a derivative of the freetype anti-aliaser.

rasterx Rasterx is a golang rasterizer that implements path stroking functions capable of SVG 2.0 compliant 'arc' joins and explicit loop closing. Pat

97 Jun 11, 2022
###### Converts PDF, DOC, DOCX, XML, HTML, RTF, etc to plain text

docconv A Go wrapper library to convert PDF, DOC, DOCX, XML, HTML, RTF, ODT, Pages documents and images (see optional dependencies below) to plain tex

959 Jun 23, 2022
###### HTML Canvas 2D Context API for mobile, desktop and web

canvas HTML Canvas 2D Context API for mobile, desktop and web Context2D API https://www.w3.org/TR/2dcontext/ native code implement https://github.com/

2 Apr 22, 2022
###### Go binding for the cairo graphics library

go-cairo Go binding for the cairo graphics library Based on Dethe Elza's version https://bitbucket.org/dethe/gocairo but significantly extended and up

118 Apr 18, 2022
###### Caigo - Golang Library for StarkNet/Cairo

Golang Library for StarkNet/Cairo Caigo is predominately a transcription of the

40 Jun 21, 2022
###### A minimalist Go PDF writer in 1982 lines. Draws text, images and shapes. Helps understand the PDF format. Used in production for reports.

one-file-pdf - A minimalist PDF generator in <2K lines and 1 file The main idea behind this project was: "How small can I make a PDF generator for it

441 May 15, 2022
###### Convert scanned image PDF file to text annotated PDF file

Jisui (自炊) This tool is PoC (Proof of Concept). Jisui is a helper tool to create e-book. Ordinary the scanned book have not text information, so you c

27 Apr 7, 2022
###### Golang PDF library for creating and processing PDF files (pure go)

UniPDF - PDF for Go UniDoc UniPDF is a PDF library for Go (golang) with capabilities for creating and reading, processing PDF files. The library is wr

1.6k Jun 27, 2022
85 May 10, 2022
###### golang 在线预览word,excel,pdf,MarkDown(Online Preview Word,Excel,PPT,PDF,Image by Golang)

Go View File 在线体验地址 http://39.97.98.75:8082/view/upload (不会经常更新，保留最基本的预览功能。服务器配置较低，如果出现链接超时请等待几秒刷新重试，或者换Chrome) 目前已经完成 docker部署 （不用为运行环境烦恼） Wor

59 Jun 14, 2022
###### A sample FaaS function that gets a stock quote and 30 day history by symbol and returns a HTML page with a generates SVG sparkline.

faas stonks This uses serverless technology to get a stock quote and 30 day sparkline from Yahoo Finance. Deployment Nimbella account Namespace with o

2 Sep 23, 2021
###### HTML, CSS and SVG static renderer in pure Go

Web render This module implements a static renderer for the HTML, CSS and SVG formats. It consists for the main part of a Golang port of the awesome W

7 Apr 19, 2022