SVG graphics

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