Snippet for Fun

TIL: Centering Text Block in SVG

Posted at — Oct 4, 2020

SVG can draws text as we do in canvas. It’s a common task to center a text block. Text block can span multiple lines. Since in SVG, the viewport origin is at the top-left corner, we can use similar transform: translate trick in CSS.

package main

import (
	"encoding/xml"
	"fmt"
	"net/http"
	"strings"

	svg "github.com/ajstarks/svgo"
)

func main() {
	lines := []string{
		"meaningful work and",
		"meaningful relationships",
		"aren't just nice thins we",
		"chose for ourselves",
		"-- they are genetically",
		"program into us.",
	}
	for idx := range lines {
		lines[idx] = strings.ToUpper(lines[idx])
	}

	http.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		w.Header().Set("Content-Type", "image/svg+xml")

		width := 600
		height := 600
		fontSize := 32

		canvas := svg.New(w)
		canvas.Start(width, height)
		canvas.Def()
		canvas.Style("text/css", "@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@500&display=swap');")
		canvas.DefEnd()
		canvas.Rect(0, 0, width, height, "fill:red")
		canvas.Gstyle(fmt.Sprintf("font-size:%dpx;fill:white;text-anchor:middle;font-family:Roboto", fontSize))
		fmt.Fprintf(
			canvas.Writer,
			`<text x="50%%" y="50%%" transform="translate(0, %d)">`,
			-len(lines)*fontSize/2,
		)
		for _, line := range lines {
			fmt.Fprintf(
				canvas.Writer,
				`<tspan x="50%%" dy="%d">`,
				fontSize,
			)
			xml.Escape(canvas.Writer, []byte(line))
			fmt.Fprintf(
				canvas.Writer,
				`</tspan>`,
			)
		}
		fmt.Fprintf(
			canvas.Writer,
			`</text>`,
		)
		canvas.Gend()
		canvas.End()
	}))

	if err := http.ListenAndServe(":3333", nil); err != nil {
		panic(err)
	}
}