All formats

zc310/ofd (OFD Go Toolkit)

LanguageGo
LicenseApache-2.0
Repositorygithub.com/zc310/ofd
Documentationpkg.go.dev: converter, pkg.go.dev: reader, Go API guide (Chinese)
Depends onpure Go; optional external tools (LibreOffice, Chrome) only for Office, HTML, EML and SVG import
Read / writeyes / yes

zc310/ofd parses, renders, converts, creates, validates, signs and encrypts OFD documents in pure Go. The library is the base of the project’s command-line tools (ofd-converter, ofd-creator, ofd-validator, ofd-invoice and others), a desktop and Android reader, and a WebAssembly reader that runs in the browser.

The main entry points:

The project is new (first release in 2026) and at version 0.x; comments and docs are in Chinese.

Installation#

Shell
go get github.com/zc310/ofd@latest

Basic usage: convert OFD to PDF#

Go
package main

import (
	"context"
	"log"
	"os"

	"github.com/zc310/ofd/pkg/converter"
)

func main() {
	out, err := os.Create("invoice.pdf")
	if err != nil {
		log.Fatal(err)
	}
	err = converter.PDF(context.Background(), "invoice.ofd", out)
	if cerr := out.Close(); err == nil {
		err = cerr
	}
	if err != nil {
		log.Fatal(err)
	}
}

The first argument is a context.Context: pass one with a timeout to limit work on untrusted files. converter.Page(n) limits output to page n (1-based).

How to extract text from an OFD file#

converter.Text writes plain text, one text object per line, with a form feed between pages:

Go
err := converter.Text(context.Background(), "invoice.ofd", os.Stdout)

For positions, use the reader package. Page indexes start at 0, and coordinates are in millimeters from the top-left corner:

Go
package main

import (
	"fmt"
	"log"

	"github.com/zc310/ofd/pkg/reader"
)

func main() {
	r, err := reader.Open("invoice.ofd") // path, []byte or io.Reader
	if err != nil {
		log.Fatal(err)
	}
	defer r.Close()

	fmt.Println(r.PageCount(), "pages")
	runs, err := r.Text(0)
	if err != nil {
		log.Fatal(err)
	}
	for _, t := range runs {
		fmt.Printf("%6.1f %6.1f  %s\n", t.X, t.Y, t.Text)
	}
}

How to convert OFD pages to PNG#

converter.Image calls your function to get a writer for each page:

Go
err := converter.Image(context.Background(), "invoice.ofd",
	converter.PNG(),
	converter.DPI(150),
	converter.BgColor(color.White),
	converter.Writer(func(page int) (io.WriteCloser, error) {
		return os.Create(fmt.Sprintf("page-%d.png", page))
	}),
)

converter.JPG(), converter.SVG() and converter.WithFormat("tiff") select other formats. To render a single page into memory, use reader.RenderPage with reader.RenderOptions{DPI: 144, Format: reader.RenderPNG}.

How to read metadata, page size and attachments#

Go
r, err := reader.Open("invoice.ofd")
if err != nil {
	log.Fatal(err)
}
defer r.Close()

info, err := r.Info()
if err == nil {
	fmt.Println(info.Title, info.Author, info.Creator, info.CreationDate)
}
if p, err := r.Page(0); err == nil {
	fmt.Printf("%.0f x %.0f mm\n", p.Width, p.Height)
}
atts, _ := r.Attachments()
for _, a := range atts {
	data, err := r.AttachmentData(a.Scope, a.ID, 0) // 0 = default size limit
	if err == nil {
		fmt.Println(a.Name, len(data), "bytes") // e-invoices attach their XML data here
	}
}

How to convert PDF to OFD#

Importers are registered by a blank import; Convert then works between any registered formats:

Go
package main

import (
	"context"
	"log"
	"os"

	"github.com/zc310/ofd/pkg/converter"
	_ "github.com/zc310/ofd/pkg/converter/import/pdf"
)

func main() {
	out, err := os.Create("out.ofd")
	if err != nil {
		log.Fatal(err)
	}
	err = converter.Convert(context.Background(), "pdf", "ofd", "in.pdf", out)
	if cerr := out.Close(); err == nil {
		err = cerr
	}
	if err != nil {
		log.Fatal(err)
	}
}

Other importers: import/image, import/markdown, import/csv, import/svg, import/xps, import/office (needs LibreOffice).

Tips#