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

Canvas

API reference Go Report Card Coverage Status Donate

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.

Preview

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.

Articles

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

Text Example

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
richText.Add(ff, "string")
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:

Stroke example

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.

License

Released under the MIT license.

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

    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.

    image

    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)
    

    image

    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.

  • Not drawing some text

    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?

  • fc-match for mac ?

    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:
    	# ! ../../text/fribidi_cgo.go:8:10: fatal error: 'fribidi.h' file not found
    	cd $(EX)/opengl && $(MAKE) all
    
    ex-run-map:
    	# ! needs "fc-match" exe
    	cd $(EX)/map && go run .
    
    

    From my limited googling it looks like fc-match is a Linux only lib, and hence why there is no brew install etc :)

  • Move origin to left-top and draw from top left to bottom right

    Move origin to left-top and draw from top left to bottom right

    From what I understand, the coordinate system is designed in such a way that the origin is in the lower left corner of canvas and that you draw from left to right and bottom to top, correct?

    Is there a way to change it so that the 0,0 point is in the upper left corner (like most design programs do) and draw from left to right and top to bottom?

  • Experiment with RenderJPEG

    Experiment with RenderJPEG

    Closes #54

    This is my experiment so far.


    The RenderImage can check if the img implements other interfaces to access to the original bytes:

    • JPEGImage (JPEGBytes)
    • PNGImage (PNGBytes)
  • panic: glGetActiveSubroutineName

    panic: glGetActiveSubroutineName

    //go:build cgo
    
    package main
    
    import (
    	"fmt"
    	"runtime"
    
    	"github.com/go-gl/gl/v4.1-core/gl"
    	"github.com/go-gl/glfw/v3.3/glfw"
    	"github.com/tdewolff/canvas"
    	"github.com/tdewolff/canvas/renderers/opengl"
    )
    
    func main() {
    	runtime.LockOSThread()
    
    	ogl := opengl.New(200.0, 100.0, canvas.DPMM(5.0))
    	//ctx := canvas.NewContext(ogl)
    	//if err := canvas.DrawPreview(ctx); err != nil {
    	//	panic(err)
    	//}
    
    	// Set up window
    	if err := glfw.Init(); err != nil {
    		panic(err)
    	}
    	defer glfw.Terminate()
    
    	glfw.WindowHint(glfw.Resizable, glfw.False)
    	glfw.WindowHint(glfw.ContextVersionMajor, 3)
    	glfw.WindowHint(glfw.ContextVersionMinor, 2)
    	glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
    	glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
    
    	width, height := 800, 400
    	window, err := glfw.CreateWindow(width, height, "", nil, nil)
    	if err != nil {
    		panic(err)
    	}
    	window.MakeContextCurrent()
    
    	if err := gl.Init(); err != nil { // panic: glGetActiveSubroutineName
    		panic(err)
    	}
    	version := gl.GoStr(gl.GetString(gl.VERSION))
    	fmt.Println("OpenGL version", version)
    
    	// Compile canvas for OpenGl
    	ogl.Compile()
    
    	gl.Enable(gl.BLEND)
    	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
    
    	gl.ClearColor(1, 1, 1, 1)
    	for !window.ShouldClose() {
    		gl.Clear(gl.COLOR_BUFFER_BIT)
    
    		// Draw compiled canvas to OpenGL
    		ogl.Draw()
    
    		glfw.PollEvents()
    		window.SwapBuffers()
    	}
    }
    

    https://github.com/tdewolff/canvas/blob/master/examples/opengl/main.go

  • Strange png rendering behavior

    Strange png rendering behavior

    Hi! First of all, thank you for creating this useful drawing tool!

    I have stubbed upon one very strange issue. I am trying to draw a contour map by using polylines. The issue appears when I am trying to draw non-filled contours with non-zero stroke width. When I use "polyline.ToPath()" the code works well. When I attempt to smoothen the line by using "polyline.Smoothen()" I see unexpected filled rectangles in the resulting PNG file. And the rectangles have purely random sizes and positions at each next run. At the same time SVG files are correct in both the cases.

    Here I attach two PNGs and zipped code. To reproduce the issue you may go to the line 259 in the file "main.go". The code is relatively simple and may be straightforwardly unzipped and built.

    20200930_3_correct 20200930_3_incorrect

    Best regards, Dmitry

  • Uneven number of pages, corrupts pdf

    Uneven number of pages, corrupts pdf

    First of all, I like to compliment you on this awesome package!

    However, I hit a snag during Multiple PDF page generation (more than 2 that is) the following code should illustrate the issue.

            buf := &bytes.Buffer{}
    	pdf := canvas.NewPDF(buf, 210, 297)
    
    	pdf.NewPage(210, 297)
    
    	pdf.NewPage(210, 297) // if this is commented out, pdf = ok
    
    	pdf.Close()
    
    	ioutil.WriteFile("out.pdf", buf.Bytes(), 0644) // more then 2 pages corrupts pdf
    
    

    Am I doing something wrong?

  • Does tdewolff/canvas currently work with go get?

    Does tdewolff/canvas currently work with go get?

    What I ran:

    go get -u github.com/tdewolff/canvas
    

    What it fetched:

    go: downloading github.com/tdewolff/canvas v0.0.0-20220113195210-2b774ce29caa
    go: downloading github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f
    go: downloading github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81
    go: downloading github.com/adrg/sysfont v0.1.2
    go: downloading github.com/tdewolff/minify/v2 v2.9.26
    go: downloading github.com/tdewolff/parse/v2 v2.5.26
    go: downloading github.com/benoitkugler/textlayout v0.0.3
    go: downloading github.com/tdewolff/parse/v2 v2.5.27
    go: downloading github.com/tdewolff/parse v1.1.0
    go: downloading github.com/dsnet/compress v0.0.1
    go: downloading github.com/tdewolff/minify v1.1.0
    go: downloading github.com/tdewolff/minify/v2 v2.10.0
    go: downloading github.com/adrg/strutil v0.2.2
    go: downloading github.com/adrg/xdg v0.4.0
    go: downloading github.com/adrg/strutil v0.2.3
    go: downloading golang.org/x/sys v0.0.0-20220209214540-3681064d5158
    go: downloading github.com/benoitkugler/textlayout v0.0.9
    go: downloading golang.org/x/exp v0.0.0-20210722180016-6781d3edade3
    go: downloading gonum.org/v1/gonum v0.9.3
    go: added github.com/adrg/strutil v0.2.3
    go: added github.com/benoitkugler/textlayout v0.0.9
    go: added github.com/tdewolff/canvas v0.0.0-20220113195210-2b774ce29caa
    go: added github.com/tdewolff/minify/v2 v2.10.0
    go: upgraded golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac => v0.0.0-20220209214540-3681064d5158
    go: upgraded gonum.org/v1/gonum v0.8.2 => v0.9.3
    

    But then trying to run code including github.com/tdewolff/canvas, I get compile-time errors:

    /go/pkg/mod/github.com/tdewolff/[email protected]/text/harfbuzz.go:23:50: too many arguments in call to truetype.Parse
            have (*bytes.Reader, bool)
            want (fonts.Resource)
    
  • Richtext: Linebreak not working on right aligned Text

    Richtext: Linebreak not working on right aligned Text

    Updating to the latest version does not allow me to create Richtext with right-aligned text where each line is aligned to the right, while maintaining automatic line breaks.

    Text Input (Each line different font Size, using \n as a manual linebreak):

    Fels der Weissagung [FR]
    + Jademeer [FR]
    
    maxWidth := float64(105)
    rowSize := float64(40)
    
    [...].ToText(maxWidth, rowSize, canvas.Right, canvas.Center, 0, 0)
    

    Before Updating: image

    After Updating: image

    You can see, that linebreaks are not beeing applied, and the text is over the maximum width. Maxiumum width is equal to the green boxes width, while the red box indicates the canvas.Text resulting Bound.

    Please advice, how to align it like it was before updating.

    Possible breaking commit: https://github.com/tdewolff/canvas/commit/386f4f513035f7744bd63e54e771f34aec07e561

    I hope you can help me with that :)

  • Add PathScanner to avoid copying in renderers

    Add PathScanner to avoid copying in renderers

    Currently the fyne and gio renderers use the Segments method to get the path data, which causes copying. We can use the Scanner pattern to prevent that. How do you feel about that?

    Here is the entire code:

    package canvas
    
    const EndOfPath = 0 // Should be declared in the same block as LineToCmd, CloseCmd, etc.
    
    type PathScanner struct {
    	p *Path
    	i int
    }
    
    func NewPathScanner(p *Path) *PathScanner {
    	return &PathScanner{p, 0}
    }
    
    func (s *PathScanner) Scan() (cmd float64, args []float64) {
    	if len(s.p.d) <= s.i {
    		return EndOfPath, nil
    	}
    	cmd = s.p.d[s.i]
    	n := cmdLen(cmd)
    	args = s.p.d[s.i+1 : s.i+n]
    	s.i += n
    	return
    }
    

    Usage:

    func _() {
    	p := canvas.Path{}
    	s := canvas.NewPathScanner(&p)
    	for {
    		cmd, args := s.Scan()
    		if cmd == canvas.EndOfPath {
    			break
    		}
    		switch cmd {
    		case canvas.MoveToCmd:
    			// ...
    		}
    	}
    }
    
  • Add zero layer above all

    Add zero layer above all

    Hi! Probably this isn't issue, but browsing through your code I coudn't find any solution. The point is that I'm trying to draw a "map" where contours (or layers) have both humps and holes (see figs attached). And in some holes the bottom layer is zero (pixels must not be filled). But when I subsequently overlay the contours and add the zero contour to the top, I see not an empty hole but the last underlying contour with minimal, but non-zero value. Could you please give some advice, how I could solve this problem?

    Thanks in advance, Dmitry

    frame_0

  • SVG text is upside down / flipped on y axis

    SVG text is upside down / flipped on y axis

    I'm sorry, I must be missing a setting somewhere but I've looked at the docs and issues ... The output SVG is upside down?

            fontFamily := canvas.NewFontFamily("Geneva")
    	if err := fontFamily.LoadFontFile("/System/Library/Fonts/Geneva.ttf", canvas.FontBold); err != nil {
    		panic(err)
    	}
    
    	face := fontFamily.Face(80.0, canvas.Black, canvas.FontBold, canvas.FontNormal)
    	path, _, err := face.ToPath(text)
    	if err != nil {
    		panic(err)
    	}
    
    	tpl := `<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" height="400" width="800">
    		<path d="%s" />
    	</svg>`
    	fmt.Printf(tpl, path.ToSVG())
    
  • any way to take a gio canvas and render it to PDF ?

    any way to take a gio canvas and render it to PDF ?

    Looking for ways to take the gio canvas and then export it as a PDF.

    Because the Canvas is rendering engine "agnostic" then maybe it's possible ?

    The reason i ask is because then i can print to a printer with gio

  • CI and github actions

    CI and github actions

    SO i was thinking that CI is really needed, and have been using this approach which you might like to use.

    Nice explicit make files: https://github.com/jerson/openpgp-mobile/blob/flatbuffers/Makefile

    Git hub actions just calls the same make files: https://github.com/jerson/openpgp-mobile/tree/flatbuffers/.github/workflows

    That gets you the ability to have the same make target run for many platforms ( desktop, mobile, wasm)

    Then because you have many examples to CI, you can wrap the calls to the make file targets with a strategy pattern like here: https://github.com/amplify-edge/main/blob/master/.github/workflows/ci.yml#L17

    Please notice down the very bottom how the make file asked for is then called.

    • https://github.com/amplify-edge/main/blob/master/.github/workflows/ci.yml#L80

    I like this pattern because the same make target is run on your laptop and in CI. Its less work to maintain and you know that whats happens locally is what happen in github ci. When you change your makefiles and you dont have to change your CI workflow files.

  • ★ Roadmap

    ★ Roadmap

    This is a roadmap of future plans with canvas, the intention is to finish all these matters, but not necessarily in a timely matter. Please see this list as possible plans. Features will be implemented in the develop branch and merged when deemed mature. Also see https://github.com/tdewolff/canvas/wiki/Planning.

    • [ ] Increase test coverage
    • [x] Parse SFNT (TTF and OTF) fonts with tables cmap, head, hhea, hmtx, maxp, name, OS/2, post, glyf, loca, kern, vhea, vmtx, CFF
    • [x] Generate subset font by defining the glyph IDs that are used (reduces font file size greatly for embedding)
    • [ ] Parse GPOS, GSUB, and GDEF tables for shaping in SVGs
    • [x] Text shaping using HarfBuzz (https://harfbuzz.github.io/ch01s03.html) to properly display Arabic, Devanagari, etc.
    • [x] Text directionality using FriBidi (https://github.com/fribidi/fribidi) in order to mix e.g. Arabic and English in one sentence
    • [x] Vertical text such as Chinese and Japanese
    • [ ] Font features and variants to enable swash or stylistic ligatures, and to change the font weight variably
    • [x] Text justification algorithm from Donald Knuth (http://www.eprg.org/G53DOC/pdfs/knuth-plass-breaking.pdf) as used in TeX
    • [x] Text itemization using a rich text implementation to allow different font faces and decoration to occur in one paragraph
    • [x] Support adding paths and images to rich text implementation for e.g. emoticons
    • [ ] Make PDFs linearized (see Annex F) if possible
    • [x] Support PDF text copying
    • [ ] Support fill patterns (from paths?) and gradients (linear, radial)
    • [x] Support path intersection to allow path merging (AND, OR, XOR)
    • [ ] Support path tessellation
    • [ ] Support OpenGL rendering using Blinn and Loop algorithm or something similar (see https://lambdacube3d.wordpress.com/2014/11/12/playing-around-with-font-rendering/ as well), also see this rust library: https://raphlinus.github.io/rust/graphics/gpu/2020/06/13/fast-2d-rendering.html
    • [x] Set Z-index for draw order for Canvas
    • [x] SetView should also change coordinate position
    • [x] Path transformation (SetView) should transform stroke as well (works only for raster, PDF, SVG)
    • [x] Dashed stroke at start/corners is not always equivalent between renderers
    • [x] EPS stroking support
    • [x] PostScript target
    • [x] Optimize stroke first point
    • [x] Support native LaTeX formulas using https://github.com/go-latex/latex, see https://github.com/tdewolff/canvas/issues/48#issuecomment-695210608
    • [x] Improve examples
    • [x] Improve readme
    • [x] Use https://github.com/benoitkugler/textlayout/tree/main/harfbuzz as a default
    • [x] Use gamma corrected blending (incl. for fonts), see https://www.puredevsoftware.com/blog/2019/01/22/sub-pixel-gamma-correct-font-rendering/, http://nothings.org/gamedev/rasterize/, https://github.com/chmike/gofontrender
    • [ ] Add Liang's hyphenation algorithm
    • [x] Add text attribute spans to RichText in order to properly track underlining for text ending in spaces (?)
    • [ ] Support target formats: PGF, JPEG2000, WEBP, CCITT, CDR (CorelDRAW), DWG (AutoCAD Drawing)
    • [ ] Add path approximator, based on a max error will linearize and simplify parts
    • [ ] Add path patterns and wall tiling support
    • [x] Support Chinese text H/V alignment and position/line-height (text-orientation)
    • [ ] Support Porter-Duffman Src blending mode for all renderers, see #193
    • [ ] Text on a path support
    • [ ] Self intersecting paths for boolean operations
Related tags
Canvas is a Go drawing library based on OpenGL or using software rendering that is very similar to the HTML5 canvas API
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

Jan 3, 2023
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 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

Nov 1, 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/

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

Dec 19, 2022
This command line converts .html file into .html with images embed.

embed-html This command line converts .html file into .html with images embed. Install > go get github.com/gonejack/embed-html Usage > embed-html *.ht

Oct 6, 2022
Publish Your GIS Data(Vector Data) to PostGIS and Geoserver
Publish Your GIS Data(Vector Data) to PostGIS and Geoserver

GISManager Publish Your GIS Data(Vector Data) to PostGIS and Geoserver How to install: go get -v github.com/hishamkaram/gismanager Usage: testdata fol

Sep 26, 2022
A Go package converting a monochrome 1-bit bitmap image into a set of vector paths.
A Go package converting a monochrome 1-bit bitmap image into a set of vector paths.

go-bmppath Overview Package bmppath converts a monochrome 1-bit bitmap image into a set of vector paths. Note that this package is by no means a sophi

Mar 22, 2022
Go Language Library for SVG generation
Go Language Library for SVG generation

SVGo: A Go library for SVG generation The library generates SVG as defined by the Scalable Vector Graphics 1.1 Specification (http://www.w3.org/TR/SVG

Jan 6, 2023
gensvg generates SVG to an io.Writer
gensvg generates SVG to an io.Writer

gensvg: A Go library for SVG generation The library generates SVG as defined by the Scalable Vector Graphics 1.1 Specification

Dec 28, 2022
A simple API written in Go that creates badges in SVG format, based on the requested route.

A simple API written in Go that creates badges in SVG format, based on the requested route. Those graphics can be used to style README.md files, or to add tags to webpages.

Jul 2, 2021
Very simple SVG to PNG converter library using the Inkscape.

svg2png Description Very simple SVG to PNG converter library using the Inkscape.

Jan 11, 2022
Snippit - Creates syntax-highlighted code snippets in png or svg format
Snippit - Creates syntax-highlighted code snippets in png or svg format

snippit creates syntax-highlighted code snippets in png or svg format. Installat

Oct 10, 2022
Go-binsize-treemap - Go binary size SVG treemap

?? Go binary size SVG treemap Make treemap breakdown of Go executable binary $ g

Dec 21, 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

Dec 4, 2022
Imgpreview - Tiny image previews for HTML while the original image is loading
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

May 22, 2022
2D rendering for different output (raster, pdf, svg)
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

Dec 25, 2022
Canvas is a Go drawing library based on OpenGL or using software rendering that is very similar to the HTML5 canvas API
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

Jan 3, 2023
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 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

Nov 1, 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

Jan 5, 2023
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/

Apr 22, 2022