Pure Go HTML/CSS to SVG/PNG/JPEG renderer for OpenGraph images.
Vercel Satori alternative. No CGo.
Documentation Β· Playground Β· Examples
- HTML + inline CSS to SVG, PNG, or JPEG
- Inline
<svg>elements and SVG data URI rasterization - Tailwind CSS v3 utility classes including gradients (no build step)
- Flexbox layout engine (W3C spec)
- Complex script rendering via pure Go HarfBuzz port (Arabic, Hebrew, Devanagari, Thai)
- RTL text support with Unicode bidi algorithm
- Emoji rendering (Twemoji, OpenMoji, Noto) in SVG and PNG
- Font embedding as SVG paths (self-contained SVGs)
- Built-in Go fonts, Google Fonts auto-fetch, CDN loading, WOFF decompression
- Linear RGB gradient interpolation with ordered dithering
- HTTP server with LRU caching, rate limiting, templates
- Go library with
http.Handlerintegration - JSX-style Go builder API
- Tailwind filter and transform classes (blur, scale, rotate, etc.)
- 95%+ pixel accuracy vs Satori across 25 test fixtures
go install github.com/macawls/ogre/cmd/ogre@latest
# Render HTML file to SVG
ogre --render template.html --output og.svg
# Render inline HTML to PNG
ogre --html '<div class="flex w-full h-full bg-blue-500 items-center justify-center"><div class="text-4xl font-bold text-white">Hello</div></div>' --output og.png --format png
# Start HTTP server
ogre --serve --port 3000CLI flags:
| Flag | Default | Description |
|---|---|---|
--serve |
false | Start HTTP server mode |
--port |
3000 | Server port |
--render |
Path to HTML file to render | |
--html |
Inline HTML string to render | |
--output |
Output file path (stdout for SVG if omitted, required for PNG/JPEG) | |
--width |
1200 | Canvas width in pixels |
--height |
630 | Canvas height in pixels |
--format |
svg | Output format: svg, png, or jpeg |
package main
import (
"os"
"github.com/macawls/ogre"
)
func main() {
result, err := ogre.Render(`
<div class="flex flex-col w-full h-full bg-slate-900 p-16 justify-center">
<div class="text-5xl font-bold text-white">My Blog Post</div>
<div class="text-xl text-slate-400 mt-4">A subtitle here</div>
</div>
`, ogre.Options{Width: 1200, Height: 630})
if err != nil {
panic(err)
}
os.WriteFile("og.svg", result.Data, 0644)
}r := ogre.NewRenderer()
// Reuses font manager across renders β thread-safe
result, _ := r.Render(html, ogre.Options{Width: 1200, Height: 630})// From file data
fontData, _ := os.ReadFile("Inter-Regular.ttf")
result, _ := ogre.Render(html, ogre.Options{
Fonts: []ogre.FontSource{{
Name: "Inter", Weight: 400, Style: "normal", Data: fontData,
}},
})
// From URL (fetched and cached automatically)
result, _ := ogre.Render(html, ogre.Options{
Fonts: []ogre.FontSource{{
Name: "Inter", Weight: 400, URL: "https://example.com/Inter-Regular.ttf",
}},
})type Options struct {
Width int // Canvas width (default 1200)
Height int // Canvas height (default 630)
Format Format // "svg" (default), "png", or "jpeg"
Quality int // JPEG quality 1-100 (default 90)
Fonts []FontSource // Custom fonts to load
Debug bool
EmojiProvider string // "twemoji" (default), "none"
}
type FontSource struct {
Name string // Font family name
Weight int // Font weight (100-900)
Style string // "normal" or "italic"
Data []byte // Raw font data (TTF/OTF/WOFF)
URL string // URL to fetch font from (alternative to Data)
}
type Result struct {
Data []byte // Rendered output bytes
ContentType string // "image/svg+xml", "image/png", or "image/jpeg"
Width int
Height int
}ogre.Render(html string, opts Options) (*Result, error)-- One-shot render. Creates a new font manager each call.ogre.NewRenderer() *Renderer-- Creates a shared renderer with pre-loaded default fonts.(*Renderer).Render(html string, opts Options) (*Result, error)-- Render with shared font manager. Thread-safe.(*Renderer).LoadFont(src FontSource) error-- Pre-load a font into the shared manager.
Render HTML to SVG, PNG, or JPEG.
Request body (JSON):
{
"html": "<div class=\"flex w-full h-full bg-blue-500\">...</div>",
"width": 1200,
"height": 630,
"format": "svg",
"quality": 90
}The quality field (1-100) controls JPEG compression. Ignored for SVG and PNG. Default 90.
Response: image bytes with appropriate Content-Type header (image/svg+xml, image/png, or image/jpeg).
Response headers include ETag, Cache-Control, and X-Cache (HIT/MISS).
Render a Go html/template with data substitution.
Request body (JSON):
{
"template": "<div class=\"text-4xl\">{{.Title}}</div>",
"data": { "Title": "Hello World" },
"width": 1200,
"height": 630,
"format": "svg"
}Response: same as /render.
Returns {"status":"ok"}.
Configurable via CORS_ORIGIN env var. Supports *, single origin, or comma-separated origins.
- Max request body size: 10 MB
- Default cache size: 64 MB (LRU, keyed by SHA-256 of input)
display: flex, none, block, contentsposition: static, relative, absolutetop,right,bottom,leftwidth,height,min-width,min-height,max-width,max-heightaspect-ratiooverflow: visible, hiddenbox-sizing: content-box, border-box
flex-direction: row, row-reverse, column, column-reverseflex-wrap: nowrap, wrap, wrap-reverseflex-grow,flex-shrink,flex-basisalign-items: auto, flex-start, flex-end, center, stretch, baseline, space-between, space-aroundalign-self: auto, flex-start, flex-end, center, stretch, baselinealign-content: auto, flex-start, flex-end, center, stretch, space-between, space-aroundjustify-content: flex-start, flex-end, center, space-between, space-around, space-evenlygap,row-gap,column-gap
margin(all sides, shorthand)padding(all sides, shorthand)border-width,border-style,border-color(all sides, shorthand)border-radius(all corners, shorthand)
font-family,font-size,font-weight,font-stylecolorline-height,letter-spacingtext-align: left, right, center, justify, start, endtext-transform: none, uppercase, lowercase, capitalizetext-decoration-line: none, underline, overline, line-throughtext-decoration-color,text-decoration-styletext-shadowwhite-space: normal, nowrap, pre, pre-wrap, pre-lineword-break: normal, break-all, break-word, keep-alltext-overflow: ellipsis-webkit-line-clamp
background-colorbackground-image(linear-gradient, radial-gradient, url())background-size,background-position,background-repeat
opacitybox-shadowtransform,transform-originobject-fit: fill, contain, cover, scale-down, noneobject-positionfilter: blur, grayscale, brightnessclip-path
margin,padding,border,border-radiusflex,gap,background,fonttext-decoration,overflowborder-top,border-right,border-bottom,border-leftborder-width,border-style,border-color
Ogre resolves Tailwind CSS v3 utility classes directly. No build step or Tailwind CLI needed.
Layout: flex, flex-row, flex-col, flex-wrap, flex-nowrap, flex-1, flex-auto, flex-initial, flex-none, flex-grow, flex-grow-0, flex-shrink, flex-shrink-0, hidden, block, relative, absolute
Alignment: items-start, items-end, items-center, items-stretch, items-baseline, justify-start, justify-end, justify-center, justify-between, justify-around, justify-evenly, self-auto, self-start, self-end, self-center, self-stretch, content-start, content-end, content-center, content-between, content-around, content-stretch
Spacing: p-{n}, px-{n}, py-{n}, pt-{n}, pr-{n}, pb-{n}, pl-{n}, m-{n}, mx-{n}, my-{n}, mt-{n}, mr-{n}, mb-{n}, ml-{n}, gap-{n}, gap-x-{n}, gap-y-{n}, space-x-{n}, space-y-{n}
Spacing scale: 0 = 0px, px = 1px, 0.5 = 2px, 1 = 4px, 1.5 = 6px, 2 = 8px, 2.5 = 10px, 3 = 12px, 3.5 = 14px, then {n} = n*4px up to 96.
Sizing: w-{n}, h-{n}, size-{n}, w-full, h-full, w-screen, h-screen, w-auto, h-auto, w-fit, h-fit, fraction values (w-1/2, w-1/3, w-2/3, w-1/4, w-3/4, etc.), min-w-0, min-w-full, min-h-0, min-h-full, min-h-screen, max-w-sm through max-w-2xl, max-w-full, max-w-none, max-h-full, max-h-screen, max-h-none
Typography: text-xs through text-9xl, font-thin through font-black, text-left, text-center, text-right, text-justify, italic, not-italic, uppercase, lowercase, capitalize, normal-case, underline, overline, line-through, no-underline, leading-none, leading-tight, leading-normal, leading-loose, leading-{n}, tracking-tighter through tracking-widest, truncate, whitespace-normal, whitespace-nowrap, whitespace-pre, whitespace-pre-wrap, line-clamp-{1-6}
Colors: text-{color}-{shade}, bg-{color}-{shade}, border-{color}-{shade}, plus text-white, text-black, text-transparent, bg-white, bg-black, bg-transparent
Available color palettes: slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose. Shades: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950.
Borders: border, border-0, border-2, border-4, border-8, border-t-{n}, border-r-{n}, border-b-{n}, border-l-{n}, border-solid, border-dashed, border-dotted, rounded-none through rounded-full
Effects: shadow-sm through shadow-2xl, shadow-none, opacity-{0-100}
Position: z-0 through z-50, z-auto, top-{n}, right-{n}, bottom-{n}, left-{n}, inset-{n}, aspect-square, aspect-video, aspect-auto
Overflow: overflow-hidden, overflow-visible
Use bracket notation for custom values:
<div
class="text-[32px] bg-[#ff5500] w-[200px] p-[20px] rounded-[12px] gap-[8px] leading-[1.5] tracking-[0.05em]"
></div>| Feature | Ogre | Satori |
|---|---|---|
| Language | Go | TypeScript |
| Output formats | SVG, PNG, JPEG | SVG only |
| Dependencies | stdlib + golang.org/x + go-text | yoga-wasm, others |
| Binary size | Single static binary | Node.js runtime |
| Tailwind support | Built-in (v3) | Via plugin |
| Font embedding | SVG paths | SVG paths |
| Layout engine | Custom flexbox (W3C spec) | Yoga (via WASM) |
| Emoji | Twemoji CDN | Twemoji CDN |
<div> default |
display: flex |
display: flex |
| PNG output | Built-in | Requires resvg |
| Pixel accuracy | 95%+ vs Satori | Reference |
docker build -t ogre .
docker run -p 3000:3000 ogreThe image uses a multi-stage build. The final image uses Google's distroless base with only the static binary.
The container starts in server mode by default, listening on port 3000.
The rendering pipeline has four stages:
- Parse (
parse/) -- HTML string is parsed into a node tree. Inline styles and class attributes are extracted. - Style (
style/) -- Tailwind classes are resolved to CSS properties. Shorthands are expanded. CSS values are parsed and computed. Properties inherit where appropriate. - Layout (
layout/) -- A custom flexbox layout engine computes the position and size of every node. Text nodes are measured using the font manager to determine line breaks and dimensions. - Render (
render/) -- The layout tree is rendered to SVG (with font glyphs converted to path data) or rasterized to PNG.
cmd/ogre/-- CLI entry point and HTTP server startupparse/-- HTML parsing, node treestyle/-- CSS property definitions, shorthand expansion, Tailwind resolver, inheritance, computed valueslayout/-- Flexbox layout engine (W3C spec, not a Yoga port)font/-- Font loading, text measurement, glyph path extraction, WOFF decompression, emoji supportrender/-- SVG generation and PNG rasterizationserver/-- HTTP API, LRU caching, template rendering
Standard library, golang.org/x/*, and one external package:
golang.org/x/net/html-- HTML parsinggolang.org/x/image/font-- Font interfaces and rasterizationgolang.org/x/image/vector-- 2D vector path rasterizationgolang.org/x/image/math/fixed-- Fixed-point math for font metricsgolang.org/x/text/unicode/bidi-- Bidirectional textgithub.com/go-text/typesetting-- Text shaping (kerning, ligatures, RTL)
