html2pdf

package module
v0.0.0-...-31212e4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 6, 2026 License: BSD-3-Clause Imports: 15 Imported by: 0

README

html2pdf

CI Go Reference

Pure-Go, zero-C, static HTML to vector PDF. No headless browser, no screenshot-then-slice: it drives go-webengine's own layout tree straight into go-pdfkit text/rect/stroke calls, so the PDF's text is real text — selectable, searchable, small — not a raster of it.

Quick start

doc, err := html2pdf.Export(htmlSource, html2pdf.Options{})
if err != nil {
    log.Fatal(err)
}
f, _ := os.Create("out.pdf")
defer f.Close()
doc.Write(f)

Or from the shell — a local file, or a page fetched through the engine's own client (its final URL then resolves relative image sources; use -base for that with -in):

go run ./cmd/html2pdf -in report.html -out report.pdf
go run ./cmd/html2pdf -url https://example.org/report -out report.pdf

Pagination

Page breaks land between atoms — a text line, or a whole <tr> — never through one. A table row that would overflow the page moves to the next page whole; a paragraph may still break between its own lines, same as printed text always has.

Layout width vs. print width

A page is laid out at Options.ViewportPx (default 1024px), then scaled down to fit the print column — not laid out directly at the print column's own width (a plain A4 page is under 650px wide). Many real pages carry a fixed-width element sized for a desktop viewport (a sidebar, a multi-column nav) that a browser's own responsive CSS only collapses below some breakpoint; laying out narrower than that breakpoint just squeezes the rest of the page into a sliver instead of dropping the sidebar. Confirmed against RFC 9110's HTML edition, whose table-of-contents sidebar did exactly this — see corpus/CORPUS.md for the before/after page counts across all 8 corpus pages.

Navigation

An <a href> becomes a link annotation — a URI action for an http(s) target, a GoTo to a named destination for a fragment that points at an element id in the document — one clickable rectangle per line the anchor spans, so the clickable area follows the text; an anchor that lays out no text at all (a link around an empty, CSS-drawn block) gets the block's own rectangle, as a browser's print does. Every element id becomes a named destination, the headings become the viewer's bookmark tree (<h1> at the top level, deeper headings nested under the last shallower one), and the <title> fills the PDF's Title unless Options.Title is set (Author, Subject, Keywords likewise). javascript:, mailto:, tel: and fragments nobody anchors are dropped rather than written as dead links.

Scope

This renders static HTML: no JavaScript, no @font-face. Text is set in the three families go-webengine's own paint package bundles — Inter (sans), Lora (serif), Go Mono (mono) — so the glyphs drawn always match the metrics the layout pass measured against; there is no web-font fetch to fail silently.

External stylesheets — <link rel="stylesheet"> and their @import chains — are fetched through the engine's own bounded loader (Engine.LoadStylesheets: 64 sheets, 4 MB each, two @import levels, 10 s) and cascaded for the print medium by default (Options.Media, CLI -media): a page's @media print rules and print-only stylesheets apply, its screen-only ones do not — a browser's print preview, where a site's navigation, sidebars and footers are usually hidden. -media screen styles the page as displayed. Width features (min-width, max-width) are evaluated at ViewportPx under either.

Images — raster <img>, <img src="*.svg"> and inline <svg> — go through the engine's own fetch/decode/size pipeline (Engine.LoadImages) and are embedded as bitmaps, so they're laid out and drawn exactly as the engine's raster canvas would draw them. A relative src resolves against Options.BaseURL; an image that fails to fetch or decode is simply left out, as on the raster canvas. This is the one place Export touches the network.

Inline-level decoration — a styled <span>, <code>, <a>… with a background, border or padding — paints too, fragmented per line the way CSS does (box-decoration-break: slice: the left border only on an element's first fragment, the right only on its last). The geometry comes from the engine's own LineBox.Inlines (go-webengine/engine#128), so a badge or pill lands exactly where the raster canvas puts it. Not painted on inline elements: border-radius, background-image/gradients, box-shadow — a fragment paints a flat background and straight borders.

Output

PDF 1.5. Content and font streams are flated; every non-stream object — the link annotations (one per clickable line), named destinations, outline items, the Info dictionary — is packed into flated object streams with a cross-reference stream (pdfkit's ObjectStreams), which is what keeps a document with thousands of links close to its text size (RFC 9110: 3 398 links, 1.06 MB; Chrome 4.5 MB). Fonts are embedded as subsets. Output is deterministic: the same input gives the same bytes.

Status

Validated three ways, all in corpus/ (in the spirit of go-webengine's own bench/):

  • a hand-built regression suite (html2pdf_test.go, ~94% statement coverage) and a corpus of 8 real public pages — corpus/CORPUS.md, with the bugs the corpus has found;
  • a timing/size bench against headless Chrome's own print-to-PDF — corpus/BENCH.md;
  • every reference reader on the machine — qpdf, poppler, MuPDF, Ghostscript, pdfium (Chrome's engine), pdf.js (Firefox's) and Quartz (Preview's) — run over every output, with Chrome's PDFs as the control: corpus/JUDGES.md. Being the smallest file means nothing if one reader in the field disagrees about it.

License

BSD-3-Clause, see LICENSE.

Documentation

Overview

Package html2pdf renders static HTML straight to a vector PDF: it drives go-webengine's own layout tree (no screenshot, no raster slicing) into go-pdfkit text/rect/stroke calls. Pagination breaks between atoms — a text line, or a whole table row — never through one; see atoms.go.

Scope

This is a static renderer: no JavaScript and no @font-face (text uses the three families go-webengine's own paint package bundles — Inter for sans, Lora for serif, Go Mono for mono — so the glyphs drawn always match the metrics layout measured against). External stylesheets — <link rel="stylesheet"> and their @import chains — are fetched through the engine's own bounded loader (Engine.LoadStylesheets) and cascaded for the print medium by default (Options.Media), so a page's @media print rules apply and its screen-only ones do not, as in a browser's print preview. Inline-level background and borders paint per line fragment from the engine's own LineBox.Inlines (box-decoration-break: slice); border-radius, background-image and box-shadow on an inline element do not.

Images — raster <img>, <img src="*.svg"> and inline <svg> — are fetched, decoded and sized by the engine's own pipeline (Engine.LoadImages) and embedded as bitmaps, so they are laid out and drawn exactly as the engine's raster canvas would. A relative src resolves against Options.BaseURL; an image that fails to fetch or decode is simply left out. Stylesheets and images are the two places Export touches the network.

Navigation

An <a href> becomes a link annotation — a URI action for an http(s) target, a GoTo to a named destination for a fragment that points at an element id in the document — one clickable rectangle per line the anchor spans, or the box of an anchor that lays out no text (see links.go). Every element id becomes a named destination, and the headings become the viewer's bookmark tree (<h1> at the top level, deeper headings nested under the last shallower one). The <title> fills the PDF's Title unless Options.Title is set.

Quick start

doc, err := html2pdf.Export(htmlSource, html2pdf.Options{})
if err != nil { ... }
f, _ := os.Create("out.pdf")
defer f.Close()
doc.Write(f)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Export

func Export(htmlSrc string, opts Options) (*pdfkit.Document, error)

Export parses htmlSrc, lays it out at opts.ViewportPx and returns a paginated pdfkit.Document — scaled to fit the page's printable width — ready to Write.

Types

type Options

type Options struct {
	PageSize pdfkit.PageSize // zero value: pdfkit.A4
	MarginMm float64         // zero value: 20

	// ViewportPx is the width (CSS px) the page is laid out against, then
	// uniformly scaled down to fit the print column. Many real pages carry a
	// fixed-width element sized for a desktop viewport — a sidebar, a
	// multi-column nav — that a browser's own responsive CSS only collapses
	// below some breakpoint. Laying out directly at the print column's actual
	// width (a plain A4 page is under 650px wide) sits below most such
	// breakpoints, so that fixed-width element squeezes the rest of the page
	// into a narrow remainder and the whole document wraps far taller than it
	// needs to — confirmed against RFC 9110's HTML edition, whose
	// table-of-contents sidebar did exactly this (428 pages laid out at the
	// print column's own ~642px width vs. 184 at 1024px). Zero value: 1024,
	// a common small-desktop/tablet breakpoint. Set below the print column's
	// own width (rare) to lay out 1:1 with no scaling.
	ViewportPx float64

	// BaseURL is the document's own URL, used to resolve a relative <img src>
	// and a relative <a href> (and to satisfy same-origin-shaped fetch logic
	// in the engine). A link whose href is this URL plus a fragment becomes an
	// in-document jump. Leave it empty for a document whose images and links
	// are all absolute or data: URIs.
	BaseURL string

	// Title, Author, Subject and Keywords fill the PDF's information
	// dictionary. An empty Title is taken from the document's <title>.
	Title, Author, Subject, Keywords string

	// Media is the CSS medium the page is styled for: "print" (the zero
	// value) applies the page's @media print rules and print-only
	// stylesheets and skips its screen-only ones — a browser's print
	// preview, where a site's navigation, sidebars and footers are usually
	// hidden; "screen" styles the page as displayed. Width features
	// (min-width, max-width) are evaluated at ViewportPx under either.
	Media string
}

Options configures a single Export call. The zero value is A4, 20mm margins on all sides, and a 1024px layout viewport.

Directories

Path Synopsis
cmd
html2pdf command
Command html2pdf renders a static HTML file, or a fetched page, to a vector PDF.
Command html2pdf renders a static HTML file, or a fetched page, to a vector PDF.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL