A PDF library for Go — layout engine, HTML to PDF, text shaping for left-to-right, right-to-left, Indic, and CJK scripts, redaction, forms, digital signatures, barcodes, page import, and PDF/A compliance.

go get github.com/carlos7ags/folio
Requires Go 1.25+. Three external dependencies, all from the Go
x/ extended standard library: golang.org/x/image (font parsing,
TIFF decoding), golang.org/x/net (HTML parsing), and
golang.org/x/text (Unicode bidirectional algorithm for RTL text).
| Language | Package | Status |
|---|---|---|
| Go | go get github.com/carlos7ags/folio |
This repo |
| Java | dev.foliopdf:folio-java |
folio-java |
| WASM | Playground | Built-in |
package main
import (
"github.com/carlos7ags/folio/document"
"github.com/carlos7ags/folio/font"
"github.com/carlos7ags/folio/layout"
)
func main() {
doc := document.NewDocument(document.PageSizeA4)
doc.Info.Title = "Hello World"
doc.SetAutoBookmarks(true)
doc.Add(layout.NewHeading("Hello, Folio!", layout.H1))
doc.Add(layout.NewParagraph(
"A PDF generated from Go code.",
font.Helvetica, 12,
))
doc.Save("hello.pdf")
}
Pass an HTML string to a document with AddHTML and folio wires the
converted content into it — normal-flow elements, @page size and margins,
running headers/footers, and absolutely/position:fixed elements alike.
Runs in-process — no subprocess, no headless browser, no external service.
import "github.com/carlos7ags/folio/document"
doc := document.NewDocument(document.PageSizeLetter)
doc.AddHTML(`
<h1>Invoice #1042</h1>
Bill to: <strong>Acme Corp</strong>
<table border="1">
<tr><th>Item</th><th>Amount</th></tr>
<tr><td>Consulting</td><td>$1,200</td></tr>
</table>
`, nil)
doc.Save("invoice.pdf")
AddHTML is the canonical entry point. When you need the raw conversion
result to inspect or transform it first, use html.ConvertFull and add it
back with doc.AddConvertResult(result) — adding result.Elements by hand
drops absolutes, margin boxes, and @page config.
Supports 40+ HTML elements, inline and <style> block CSS, flexbox, CSS grid,
SVG, named/hex/rgb colors, @page rules, multi-level list numbering via CSS
counters, and tables with colspan/rowspan.
For the full list of recognized CSS properties, accepted value forms, and known unsupported features, see docs/CSS_SUPPORT.md.
Try HTML to PDF live in your browser
Folio uses a plan-based layout engine — layout is a pure function with no mutation during rendering. Elements can be laid out multiple times safely, which makes page break splitting clean and predictable.
doc := document.NewDocument(document.PageSizeLetter)
doc.Info.Title = "Quarterly Report"
doc.Info.Author = "Finance Team"
doc.SetAutoBookmarks(true)
doc.Add(layout.NewHeading("Q3 Revenue Report", layout.H1))
doc.Add(layout.NewParagraph("Revenue grew 23% year over year.",
font.Helvetica, 12).
SetAlign(layout.AlignJustify).
SetSpaceAfter(10))
tbl := layout.NewTable().SetAutoColumnWidths()
h := tbl.AddHeaderRow()
h.AddCell("Product", font.HelveticaBold, 10)
h.AddCell("Units", font.HelveticaBold, 10)
h.AddCell("Revenue", font.HelveticaBold, 10)
r := tbl.AddRow()
r.AddCell("Widget A", font.Helvetica, 10)
r.AddCell("1,200", font.Helvetica, 10)
r.AddCell("$48,000", font.Helvetica, 10)
doc.Add(tbl)
doc.Save("report.pdf")
| Element | Description |
|---|---|
Paragraph |
Word-wrapped text with alignment, leading, orphans/widows |
Heading |
H1-H6 with preset sizes, spacing, and auto-bookmarks |
Table |
Borders, colspan, rowspan, header repetition, auto-column widths |
List |
Bullet, numbered, Roman, alpha, nested |
Div |
Container with borders, background, padding |
Flex |
Flexbox layout with direction, wrap, alignment |
Image |
JPEG, PNG, TIFF with aspect ratio preservation |
LineSeparator |
Horizontal rule (solid, dashed, dotted) |
TabbedLine |
Tab stops with dot leaders (for TOCs) |
Link |
Clickable text with URL or internal destination |
Float |
Left/right floating with text wrap |
Columns |
Multi-column layout with automatic balancing |
AreaBreak |
Explicit page break |
BarcodeElement |
Code128, QR, EAN-13 inline in layout |
p := layout.NewStyledParagraph(
layout.NewRun("Normal text ", font.Helvetica, 12),
layout.NewRun("bold ", font.HelveticaBold, 12),
layout.NewRun("colored and underlined", font.Helvetica, 12).
WithColor(layout.ColorRed).
WithUnderline(),
)
doc.Add(p)
Folio ships script-specific shapers and the OpenType tables they consume:
| Script family | Pipeline |
|---|---|
| Left-to-right (Latin, Cyrillic, Greek) | Standard glyph run with GSUB ligatures and GPOS kerning |
| Right-to-left (Arabic, Hebrew, Farsi) | UAX #9 bidi, Arabic contextual shaping (init/medi/fina/isol) via GSUB, kashida justification, /ActualText markers for copy/paste fidelity |
| Indic (Devanagari — Hindi, Sanskrit, Marathi, Nepali) | Five-phase OpenType pipeline: reordering, half-form substitution, conjunct formation |
| CJK (Japanese, Chinese, Korean) | Embedded TrueType subset, line-breaking with JIS X 4051 kinsoku shori |
The layout engine segments text by Unicode script (UAX #24), splits by
grapheme cluster (UAX #29), and dispatches to the appropriate shaper.
HTML inherits the same pipeline; add dir="rtl" or direction: rtl
to flip paragraph and list direction.
See examples/rtl and examples/indic
for runnable demos.
tbl := layout.NewTable().SetAutoColumnWidths()
// Or explicit widths:
tbl.SetColumnUnitWidths([]layout.UnitValue{
layout.Pct(30), layout.Pct(70),
})
// Header rows repeat automatically on page breaks
h := tbl.AddHeaderRow()
h.AddCell("Name", font.HelveticaBold, 10)
h.AddCell("Value", font.HelveticaBold, 10)
r := tbl.AddRow()
cell := r.AddCell("Styled cell", font.Helvetica, 10)
cell.SetBorders(layout.AllBorders(layout.DashedBorder(1, layout.ColorBlue)))
cell.SetBackground(layout.ColorLightGray)
cell.SetVAlign(layout.VAlignMiddle)
import "github.com/carlos7ags/folio/barcode"
qr, _ := barcode.NewQR("https://example.com")
doc.Add(layout.NewBarcodeElement(qr, 100).SetAlign(layout.AlignCenter))
bc, _ := barcode.NewCode128("SHIP-2024-001")
doc.Add(layout.NewBarcodeElement(bc, 200))
ean, _ := barcode.NewEAN13("590123412345")
doc.Add(layout.NewBarcodeElement(ean, 150))
import "github.com/carlos7ags/folio/forms"
form := forms.NewAcroForm()
form.Add(forms.NewTextField("name", [4]float64{72, 700, 300, 720}, 0))
form.Add(forms.NewCheckbox("agree", [4]float64{72, 670, 92, 690}, 0, false))
form.Add(forms.NewDropdown("role", [4]float64{72, 640, 250, 660}, 0,
[]string{"Developer", "Designer", "Manager"}))
doc.SetAcroForm(form)
doc.Save("form.pdf")
import "github.com/carlos7ags/folio/sign"
signer, _ := sign.NewLocalSigner(privateKey, []*x509.Certificate{cert})
signed, _ := sign.SignPDF(pdfBytes, sign.Options{
Signer: signer,
Level: sign.LevelBB,
Reason: "Approved",
Location: "New York",
})
os.WriteFile("signed.pdf", signed, 0644)
Supports PAdES B-B, B-T (timestamped), and B-LT (long-term validation with
embedded OCSP responses and CRLs). Also supports external signers (HSM, KMS)
via the Signer interface. Uses Go stdlib crypto.
import "github.com/carlos7ags/folio/reader"
// Read
r, _ := reader.Load("document.pdf")
fmt.Println("Pages:", r.PageCount())
page, _ := r.Page(0)
text, _ := page.ExtractText()
// Merge
r1, _ := reader.Load("doc1.pdf")
r2, _ := reader.Load("doc2.pdf")
m, _ := reader.Merge(r1, r2)
m.SaveTo("merged.pdf")
Permanently remove sensitive text from PDFs — not just a visual overlay, but actual removal of text operators from content streams.
// By text search
m, _ := reader.RedactText(r, []string{"John Doe", "555-12-3456"}, nil)
m.SaveTo("redacted.pdf")
// By regex (e.g. SSNs)
re := regexp.MustCompile(`\d{3}-\d{2}-\d{4}`)
m, _ := reader.RedactPattern(r, re, &reader.RedactOptions{
OverlayText: "REDACTED",
StripMetadata: true,
})
m.SaveTo("redacted.pdf")
Character-level precision — partial words within a line are removed without
affecting adjacent text. See examples/redact/ for a full demo.
Load existing PDFs as templates and add dynamic content on top — the standard workflow for invoices, receipts, certificates, and letterheads.
r, _ := reader.Load("template.pdf")
imp, _ := reader.ExtractPageImport(r, 0)
doc := document.NewDocument(document.PageSizeLetter)
p := doc.AddPage()
p.ImportPage(imp.ContentStream, imp.Resources, imp.Width, imp.Height)
p.AddText("Invoice #1042", font.HelveticaBold, 14, 72, 700)
doc.Save("filled.pdf")
All resources (fonts, images, color spaces) are resolved and
self-contained in the imported page.
See examples/import-page/ for a receipt-filling demo.
doc.SetFooter(func(ctx document.PageContext, page *document.Page) {
text := fmt.Sprintf("Page %d of %d", ctx.PageIndex+1, ctx.TotalPages)
page.AddText(text, font.Helvetica, 9, 280, 30)
})
doc.SetWatermarkConfig(document.WatermarkConfig{
Text: "DRAFT",
FontSize: 72,
Opacity: 0.15,
Angle: 45,
})
doc.SetTagged(true) // PDF/UA — screen readers, text extraction
doc.SetPdfA(document.PdfAConfig{Level: document.PdfA2B}) // archival
doc.SetAutoBookmarks(true) // auto-generate from headings
doc.SetPageLabels(
document.PageLabelRange{PageIndex: 0, Style: document.LabelRomanLower},
document.PageLabelRange{PageIndex: 4, Style: document.LabelDecimal},
)
layout.ColorRed // 16 named colors
layout.RGB(0.2, 0.4, 0.8) // RGB
layout.CMYK(1, 0, 0, 0) // CMYK for print
layout.Hex("#FF8800") // hex string
layout.Gray(0.5) // grayscale
go install github.com/carlos7ags/folio/cmd/folio@latest
folio merge -o combined.pdf doc1.pdf doc2.pdf
folio info document.pdf
folio text document.pdf
folio blank -o empty.pdf -size a4 -pages 5
Folio exports a C ABI (libfolio.so / .dylib / .dll) with 372 functions,
usable from Python, Ruby, C#, Java, or any language with FFI support.
CGO_ENABLED=1 go build -buildmode=c-shared -o libfolio.so ./export/
#include "folio.h"
uint64_t doc = folio_document_new(595.28, 841.89);
uint64_t page = folio_document_add_page(doc);
folio_page_add_text(page, "Hello from C", folio_font_helvetica(), 24, 72, 750);
folio_document_save(doc, "hello.pdf");
folio_document_free(doc);
Pre-built binaries for Linux, macOS, and Windows are attached to each GitHub release.
All benchmarks run the full pipeline: HTML parsing, CSS cascade, layout, and PDF serialization. Each document uses real styling: grid, flexbox, border-radius, alternating rows, and page breaks. No headless browser, no external process.
Benchmarks on Apple M1 Pro (go test -bench):
| Benchmark | What it generates | Time |
|---|---|---|
| HTMLInvoice | Styled invoice: CSS Grid, flexbox, cards, 3-row table | 1.1 ms |
| HTMLReport | 2-page quarterly report: KPI cards, 3 tables, page break | 7.9 ms |
| HTMLTableHeavy100 | 100-row, 5-column data table with alternating rows | 11.3 ms |
| BlankPage | Empty page + PDF serialization | 5.4 µs |
| SingleParagraph | One paragraph, end-to-end | 130 µs |
| Table10x3 | 10-row table via layout API | 400 µs |
Reproduce locally:
go test -run='^$' -bench=. -benchmem ./document/
The writer offers five opt-in passes via document.WriteOptions. The
zero value preserves byte-identical output; each toggle refuses on
encrypted documents.
doc.SaveWithOptions("out.pdf", document.WriteOptions{
UseXRefStream: true, // §7.5.8 cross-reference stream
UseObjectStreams: true, // §7.5.7 compressed object streams
OrphanSweep: true, // drop unreachable objects
CleanContentStreams: true, // §7.8 empty q/Q, identity cm
DeduplicateObjects: true, // merge byte-identical objects
RecompressStreams: true, // re-Flate at BestCompression
})
Savings depend on document shape. Text-heavy documents generated by the layout engine are already Flate-compressed, so the win is modest. Documents assembled by importing pages from parsed source PDFs see the largest reduction because imported content streams arrive in raw form:
| Fixture | Default | xref + obj | Full stack | Saved |
|---|---|---|---|---|
| Text-heavy report (25 sections) | 6913 B | 5621 B | 5555 B | 19.6% |
| 50 empty pages | 6317 B | 932 B | 535 B | 91.5% |
| 60-row data table | 8063 B | 7785 B |
$ claude mcp add folio \
-- python -m otcore.mcp_server <graph>