SVG graphics
The svgreader package converts SVG graphics to PDF drawing operators. No external tools (like Inkscape) are needed.
Supported SVG subset
| Element | Attributes |
|---|---|
<path> |
All path commands: M, L, H, V, C, S, Q, T, A, Z |
<rect> |
x, y, width, height, rx, ry (rounded corners) |
<circle> |
cx, cy, r |
<ellipse> |
cx, cy, rx, ry |
<line> |
x1, y1, x2, y2 |
<polyline> |
points |
<polygon> |
points |
<g> |
Groups with transform and style inheritance |
<text> |
Via TextRenderer interface (see below) |
Styling: fill, stroke, stroke-width, stroke-linecap, stroke-linejoin, fill-rule, opacity. Colors as hex (#rgb, #rrggbb), named colors, or rgb(). Inline CSS via the style attribute is supported.
Transforms: translate(), rotate(), scale(), matrix(), skewX(), skewY().
Including an SVG
import (
"strings"
"github.com/boxesandglue/boxesandglue/backend/bag"
"github.com/boxesandglue/boxesandglue/backend/node"
"github.com/boxesandglue/svgreader"
)First, parse the SVG document:
svgDoc, err := svgreader.Parse(strings.NewReader(svgString))or from a file:
file, err := os.Open("diagram.svg")
svgDoc, err := svgreader.Parse(file)Then create a node with CreateSVGNodeFromDocument:
svgNode := f.Doc.CreateSVGNodeFromDocument(svgDoc, bag.MustSP("80mm"), 0)The third argument is the height. When set to 0, the height is calculated automatically from the SVG’s aspect ratio (viewBox). Similarly, you can set the width to 0 and specify only the height. If both are 0, the SVG’s natural dimensions are used.
Place it on the page like any other node:
vl := node.Vpack(svgNode)
p.OutputAt(bag.MustSP("2cm"), bag.MustSP("26cm"), vl)Complete example
package main
import (
"log"
"strings"
"github.com/boxesandglue/boxesandglue/backend/bag"
"github.com/boxesandglue/boxesandglue/backend/node"
"github.com/boxesandglue/boxesandglue/frontend"
"github.com/boxesandglue/svgreader"
)
const mySVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 130">
<rect x="10" y="10" width="180" height="110" rx="8"
fill="#eef" stroke="#336" stroke-width="2"/>
<circle cx="100" cy="65" r="40"
fill="none" stroke="red" stroke-width="1.5"/>
<path d="M 30 100 C 60 30, 140 30, 170 100"
fill="none" stroke="blue" stroke-width="2"/>
</svg>`
func main() {
f, err := frontend.New("svg-example.pdf")
if err != nil {
log.Fatal(err)
}
svgDoc, err := svgreader.Parse(strings.NewReader(mySVG))
if err != nil {
log.Fatal(err)
}
p := f.Doc.NewPage()
svgNode := f.Doc.CreateSVGNodeFromDocument(svgDoc, bag.MustSP("60mm"), 0)
p.OutputAt(bag.MustSP("2cm"), bag.MustSP("26cm"), node.Vpack(svgNode))
p.Shipout()
f.Doc.Finish()
}Text in SVG
SVG <text> elements require a TextRenderer to convert text into PDF operators with proper font shaping. Without a renderer, text elements are silently skipped.
To enable text rendering, implement the svgreader.TextRenderer interface and pass it in RenderOptions:
type TextRenderer interface {
RenderText(text string, x, y, fontSize float64,
fontFamily, fontWeight, fontStyle string,
fill svgreader.Color) string
}The RenderText method receives the text content, position, font properties, and fill color. It returns a string of PDF operators (e.g. BT ... ET) that render the text.
Using SVGTextRenderer
The frontend package includes SVGTextRenderer, a ready-made implementation that uses the document’s font system. Font-family names in the SVG are resolved against the document’s registered FontFamilies:
tr := frontend.NewSVGTextRenderer(f)
tr.DefaultFamily = myFontFamily // fallback for unresolved font-family names
svgNode := f.Doc.CreateSVGNodeFromDocument(svgDoc, bag.MustSP("80mm"), 0, tr)Low-level approach
You can also use RenderPDF directly for full control:
svgDoc, _ := svgreader.Parse(reader)
stream := svgDoc.RenderPDF(svgreader.RenderOptions{
Width: width,
Height: height,
TextRenderer: myTextRenderer,
})
rule := node.NewRule()
rule.Width = bag.ScaledPointFromFloat(width)
rule.Height = bag.ScaledPointFromFloat(height)
rule.Hide = true
rule.Pre = stream