Skip to content

Canvas & Context

Taco de Wolff edited this page Apr 11, 2023 · 6 revisions

The library specifies two main structures for handling drawing. One is Canvas and the other is Context. We can think of the first as the drawing target and the second as the drawer. That is, Context will keep a context of drawing parameters and a set of functions to perform drawing operations and keep a state (such as color, position, ...). It will draw to a Renderer interface, which is implemented by Canvas but also by the individual renderers (rasterizer, PDF, EPS, ...). That means we can draw directly to any given renderer, including Canvas which is really just an array of records of previous drawing commands. From Canvas we can draw to any other renderer any number of times by reproducing the drawing commands.

Canvas

Typical usage of a canvas is to accumulate drawing commands and to redirect those commands to another renderer. The commands are accumulated in a stack, adding layers for every call to the implemented functions for the Renderer interface (see Renderers).

// Create a new canvas with width and height in millimeters
c := canvas.New(width, height)

// ... drawing operations

// Set the z-index of the drawing operations to change the drawing order
c.SetZIndex(-10)

// ... drawing operations

// Change the canvas' size to fit all drawn objects with a 5 millimeter margin
c.Fit(5.0)

// Write to multiple renderers: a PNG renderer at 96 DPI and an SVG renderer
renderers.Write("output.png", c, canvas.DPI(96.0))
renderers.Write("output.svg", c)

// Or to specify additional renderer settings
renderers.Write("output.pdf", c, &pdf.Options{
    Compress: false,
})

Context

Besides implementing the Renderer interface as well, it provides functions for path construction too (see Paths). The context keeps a rendering state, including draw style (fill color, stroke color, stroke width, stroke capper, stroke joiner, dash dashes, fill rule), a view, and a coordinate view. We can change the drawing style using the following functions:

SetFill(ipaint interface{}) // accepts color, gradient, or pattern
SetFillColor(col color.Color)
SetFillGradient(gradient Gradient)
SetFillPattern(pattern Pattern)
SetStroke(ipaint interface{}) // accepts color, gradient, or pattern
SetStrokeColor(col color.Color)
SetStrokeGradient(gradient Gradient)
SetStrokePattern(pattern Pattern)
SetStrokeWidth(width float64)
SetStrokeCapper(capper Capper)
SetStrokeJoiner(joiner Joiner)
SetDashes(offset float64, dashes ...float64)
SetFillRule(rule FillRule)

Push()  // push the style on the stack
Pop()   // pop a style off the stack
ResetStyle()

There are two types of transformations that happen for each drawing action: the view transformation and the coordinate view transformation. As the name suggests, the latter transforms only the coordinate at which we will be drawing a path/text/image, and will not actually transform the path/text/image itself. This is useful for creating subregions in your canvas in different coordinate systems. In contrast, the view transformation will transform the coordinate and the object itself. We can change these transformations using the following functions:

SetCoordRect(rect Rect, width, height float64)
SetCoordSystem(coordSystem CoordSystem)  // e.g. CartesianI or CartesianIV
SetCoordView(coordView Matrix)

SetView(view Matrix)
ComposeView(view Matrix)
Translate(x, y float64)
Rotate(rot float64)
RotateAbout(rot, x, y float64)
Scale(sx, sy float64)
ScaleAbout(sx, sy, x, y float64)
ReflectX()
ReflectXAbout(x float64)
ReflectY()
ReflectYAbout(y float64)
ResetView()

Furthermore, for path construction the following functions are provided:

MoveTo(x, y float64)
LineTo(x, y float64)
QuadTo(cpx, cpy, x, y float64)
CubeTo(cpx1, cpy1, cpx2, cpy2, x, y float64)
ArcTo(rx, ry, rot float64, large, sweep bool, x, y float64)
Arc(rx, ry, rot, theta0, theta1 float64)
Close()

Fill()
FillStroke()
Stroke()

Finally, to draw path/text/image objects, we can use:

DrawPath(x, y float64, paths ...*Path)
DrawText(x, y float64, texts ...*Text)
DrawImage(x, y float64, img image.Image, resolution Resolution)
FitImage(img image.Image, rect Rect, fit ImageFit) // draw image in rect

Example usage of contexts

f, err := os.Create("output.pdf")
if err != nil {
    panic(err)
}
defer f.Close()

// Create a PDF renderer
pdf := pdf.New(f, 100, 100)
defer pdf.Close()

// Setup a context around the renderer
ctx := canvas.NewContext(pdf)

// Set style
ctx.SetFillColor(canvas.Lightblue)
ctx.SetStrokeColor(canvas.Blue)
ctx.SetStrokeWidth(0.5)
ctx.SetDashes(0.0, 2.0, 1.0)  // dash of 2mm and gap of 1mm, starting at offset 0mm in the path

// Construct path
ctx.MoveTo(10, 50)
ctx.LineTo(30, 80)
ctx.QuadTo(60, 80, 90, 50)
ctx.Close()

// Fill and stroke the path
ctx.FillStroke()

// Draw an image
ctx.DrawImage(30, 10, img, canvas.DPI(96.0))
Clone this wiki locally