Turn HTML into EPUB3 and Kobo kepub files, entirely in memory, with no
external binaries. This is a Go port of @voidberg/quarto
(TypeScript). It is pure Go with no cgo, so it cross-compiles to a static ARM
binary and runs natively on e-readers like Kobo and reMarkable, as well
as in any other Go program.
go get github.com/voidberg/quarto-goThe module path ends in quarto-go, but the package is named quarto, so you
call quarto.Generate(...):
import "github.com/voidberg/quarto-go"package main
import (
"context"
"os"
"github.com/voidberg/quarto-go"
)
func main() {
in := quarto.Input{
Title: "On the Shortness of Life",
Authors: []string{"Seneca"},
Chapters: []quarto.Chapter{
{Title: "I", HTML: "<p>It is not that we have a short time to live...</p>"},
},
}
epub, err := quarto.Generate(context.Background(), in)
if err != nil {
panic(err)
}
os.WriteFile("seneca.epub", epub, 0o644)
// A Kobo kepub in one call (Generate + ToKepub):
kepub, err := quarto.GenerateKepub(context.Background(), in)
if err != nil {
panic(err)
}
os.WriteFile("seneca.kepub.epub", kepub, 0o644)
}- Valid EPUB3, checked against EPUBCheck.
- Native kepub conversion (
ToKepub): Kobo reading-location spans and thebook-columns/book-innerwrappers, with nokepubifybinary. - Optional table of contents (on by default, off with
NoToc). - Parses messy HTML fragments and re-serializes them as well-formed XHTML.
- Downloads and embeds remote images so the book is self-contained, or drops
them with
NoDownloadImages. - Deterministic: the same input always produces the same bytes (a stable FNV-1a id, no wall-clock or randomness).
- One non-stdlib dependency,
golang.org/x/net.
func Generate(ctx context.Context, in Input) ([]byte, error) // HTML -> EPUB3
func ToKepub(data []byte) ([]byte, error) // EPUB3 -> kepub
func GenerateKepub(ctx context.Context, in Input) ([]byte, error) // Generate then ToKepub
func ImageSize(data []byte, mime string) (width, height int, ok bool)context.Context is the first argument so image downloads honor cancellation
and deadlines. Functions return errors; they never panic (an empty Chapters
is an error, for example).
Build a book from an Input. The zero value picks quarto's defaults, so you
only set what you need.
| Field | Default | Notes |
|---|---|---|
Title |
(required) | Book title. |
Authors |
none | Zero or more dc:creator values. |
Publisher, Description |
none | Dublin Core metadata. |
Language |
"en" |
BCP-47 tag. |
ID |
deterministic urn:uuid |
Derived from title and authors when unset. |
Date |
none | ISO-8601 publication date. |
Series, SeriesIndex |
none | Emits both EPUB3 belongs-to-collection and Calibre series metadata. SeriesIndex is a *float64 because 0 is a valid index. |
NoToc |
TOC on | Set to suppress the visible table of contents. |
TocTitle |
"Table of Contents" |
Heading for the TOC page. |
CSS, NoCSS |
bundled stylesheet | Override with your own CSS, or ship none. |
NoDownloadImages |
downloads on | Set to drop every non-data: image instead of fetching. |
Client |
http.DefaultClient |
Inject your own *http.Client for timeouts, transport, or fixed bytes in tests. |
Per chapter (Chapter):
| Field | Notes |
|---|---|
Title |
Used in the nav/TOC and as the page <h1>. |
HTML |
The body fragment; need not be well-formed. |
Author |
Optional byline under the title. |
ExcludeFromToc |
Keep the chapter in reading order but out of the nav and NCX (useful for front matter). |
SkipTitle |
Don't generate the <h1> heading; render only your HTML. |
- Supply a cover by URL (
CoverURL) or raw bytes (CoverData); bytes win when both are set.CoverBackgroundsets the letterbox color on the cover page. CoverFromLeadImagepromotes a chapter's leading image to the cover when no explicit cover is given.TransformImageruns on each fetched image before it is embedded, so you can transcode, resize, or drop it.TransformCoverbuilds the final cover and runs even when there is no source image, so you can compose one from metadata.
With no cgo and no external binaries, a CGO_ENABLED=0 cross-build produces a
static binary you can copy straight onto an ARM Linux device and run:
# 32-bit ARMv7 — every Kobo (all models), reMarkable 1 / 2:
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -o quarto .
# ARM64 — reMarkable Paper Pro, Paper Pro Move, Paper Pure:
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o quarto .Every Kobo ships a 32-bit userspace, even newer models (Sage, Elipsa, Libra /
Clara Colour) whose chips are 64-bit capable, so GOARCH=arm is right for any
Kobo. When in doubt, run uname -m on the device over SSH: armv7l → the first
build, aarch64 → the second.
Pick the output format per device:
- Kobo: call
GenerateKepubfor the Kobo-optimised kepub. - reMarkable (and any standard EPUB reader): call
Generate. reMarkable reads EPUB natively; the Kobo-specific kepub extensions aren't needed.
Go 1.25 or newer.
This is a v0.x release: the API may still change. It will stabilize at
v1.0.0 once the first consumer (instakobo)
has integrated it.
quarto-go and the TypeScript @voidberg/quarto are parallel packages. The Go
port was validated byte-for-byte against the published TypeScript library across
a fixture corpus, but the two might evolve independently in the future.
MIT. See LICENSE.
