paper

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 10, 2026 License: MIT Imports: 19 Imported by: 0

README

Paper

pkg.go.dev Go Report Card CI Lint Codecov License

Paper is a Go library for creating PDF documents from HTML and structured components.

Use it when you want a direct HTML-to-PDF path, or when you need programmatic layout with rows, columns, headers, footers, metrics, tests, and explicit control over document generation.

Paper logo

Installation

go get github.com/avdoseferovic/paper

The optional structure assertion helper is published as a separate module so normal Paper consumers do not pull assertion-library dependencies:

go get github.com/avdoseferovic/paper/pkg/test

HTML to PDF

For HTML-only documents, use paper.FromHTML.

package main

import (
	"log"

	"github.com/avdoseferovic/paper"
)

func main() {
	doc, err := paper.FromHTML(`<h1>Hello</h1><p>World</p>`)
	if err != nil {
		log.Fatal(err)
	}

	if err := doc.Save("out.pdf"); err != nil {
		log.Fatal(err)
	}
}

Use paper.FromHTMLReader when the source HTML is already available as an io.Reader. For advanced HTML options such as asset base directories, call pkg/html directly and add the returned rows to a paper.New(...) document.

For mixed layouts, HTML fragments can also be used as regular components via github.com/avdoseferovic/paper/pkg/components/html:

htmlBlock, err := htmlcomponent.New(`<h2>Terms</h2><p>Rendered from HTML.</p>`)
if err != nil {
	log.Fatal(err)
}

doc := paper.New()
doc.AddAutoRow(
	col.New(6).Add(text.New("Direct Paper component")),
	col.New(6).Add(htmlBlock),
)

Component Layout

Use the row and column API when a document needs manual layout, repeated headers or footers, generated pages, metrics, or a testable component tree.

package main

import (
	"log"

	"github.com/avdoseferovic/paper"
	"github.com/avdoseferovic/paper/pkg/components/col"
	htmlcomponent "github.com/avdoseferovic/paper/pkg/components/html"
	"github.com/avdoseferovic/paper/pkg/components/row"
	"github.com/avdoseferovic/paper/pkg/components/text"
	"github.com/avdoseferovic/paper/pkg/config"
	"github.com/avdoseferovic/paper/pkg/props"
)

func main() {
	cfg := config.NewBuilder().
		WithLeftMargin(15).
		WithTopMargin(15).
		Build()

	doc := paper.New(cfg)
	htmlBlock, err := htmlcomponent.New(`<p>HTML fragment inside the grid</p>`)
	if err != nil {
		log.Fatal(err)
	}

	doc.AddRows(
		row.New(12).Add(
			col.New(12).Add(text.New("Invoice", props.Text{Size: 18})),
		),
		row.New().Add(
			col.New(6).Add(text.New("Programmatic content")),
			col.New(6).Add(htmlBlock),
		),
		text.NewRow(8, "Generated with Paper"),
	)

	pdf, err := doc.Generate()
	if err != nil {
		log.Fatal(err)
	}

	if err := pdf.Save("invoice.pdf"); err != nil {
		log.Fatal(err)
	}
}

Paper uses a grid-based layout model. Rows stack vertically, columns split a row across the configured grid width (12 columns by default, configurable with WithMaxGridSize), and new pages are added automatically when content exceeds the useful page area after margins, headers, and footers are reserved.

Key Features

  • HTML-to-PDF conversion through paper.FromHTML, paper.FromHTMLReader, and pkg/html.
  • Programmatic PDF layout with rows, columns, text, images, codes, tables, signatures, page numbers, headers, and footers.
  • Document output as bytes, base64, saved files, or merged PDFs.
  • PDF permission protection for casual copy/print deterrence, not confidentiality-grade encryption. RC4 is the compatibility default; AES-128 is available with WithProtectionAlgorithm.
  • Component-tree inspection through GetStructure, designed for deterministic unit tests.
  • Optional generation metrics through decorator.NewMetrics.
  • Internal PDF backend ownership, so application code depends on Paper's public packages rather than a third-party renderer API.

Performance

Generation is benchmarked in paper_benchmark_test.go. Two benchmarks live there:

  • BenchmarkPDFGeneration — representative documents (text-heavy, mixed components, HTML translation, full HTML demo).
  • BenchmarkPDFScaling — a text document swept across 10–1000 rows to expose the per-row / per-page cost curve.

Run them with:

# Representative scenarios
go test -run='^$' -bench=BenchmarkPDFGeneration -benchmem -count=6 .

# Size scaling curve
go test -run='^$' -bench=BenchmarkPDFScaling -benchmem -count=6 .

The numbers below are the median of 6 checked runs on an Apple M1 Pro (Go 1.26.4), single-threaded. They are representative of the bundled fixtures, not a universal guarantee — actual time scales with page count, image size, and component mix.

Representative documents (BenchmarkPDFGeneration)
Scenario Document Time / doc Mem / doc Allocs / doc
TextHeavy 180 text rows (~6 pages) 1.13 ms 1.31 MiB 9,501
HTMLDemoTranslateOnly HTML → component rows without PDF generation 1.43 ms 2.00 MiB 12,807
MixedComponents 40× (barcode + QR + image + signature + text) 5.11 ms 3.66 MiB 12,799
HTMLDemoFull HTML → PDF: header + styled body + embedded PNG 6.01 ms 9.72 MiB 38,271
Size scaling (BenchmarkPDFScaling)
Rows ~Pages (A4) Time / doc Mem / doc Allocs / doc
10 1 0.32 ms 165 KiB 2,276
50 2 0.51 ms 435 KiB 3,963
100 4 0.75 ms 768 KiB 6,096
500 17 2.77 ms 3.21 MiB 23,084
1000 34 5.24 ms 6.32 MiB 44,265

The curve is linear, giving a simple cost model for text content:

time(N rows) ≈ 0.27 ms (fixed setup) + 5.0 µs × N

That is roughly ~145 µs per A4 page and ~42 allocations per row. Generation is single-threaded and the internal compression writers are pooled in a concurrency-safe way, so throughput scales ~linearly across cores when generating documents in parallel.

Documentation

Development

Command Description
make build Build the project
make test Run unit tests
make fmt Format Go files
make lint Run lint checks
make dod Run the local definition-of-done checks
make examples Run documentation examples
make docs Start the local docs server

Credits

Paper is derived from and inspired by Maroto, created by Johnathan Fercher da Rosa and contributors. The original project established the Bootstrap-style row and column PDF authoring model that Paper continues to evolve.

Logo art credit remains with @marinabankr.

License

Paper is released under the MIT License.

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrCannotGenerateInLowMemoryMode       = errors.New("an error has occurred while trying to generate PDFs in low memory mode")
	ErrCannotGenerateInParallelMode        = errors.New("an error has occurred while trying to generate PDFs concurrently")
	ErrFooterHeightIsGreaterThanUsefulArea = errors.New("footer height is greater than page useful area")
	ErrHeaderHeightIsGreaterThanUsefulArea = errors.New("header height is greater than page useful area")
)

Functions

func FromHTML

func FromHTML(htmlStr string, cfgs ...*entity.Config) (*core.Pdf, error)

FromHTML converts an HTML string directly into a PDF document. It is the shortest path for callers that only need HTML-to-PDF output. Optional configs are the same configs accepted by New.

Example

ExampleFromHTML demonstrates the shortest path from HTML to PDF.

package main

import (
	"log"

	"github.com/avdoseferovic/paper"
)

func main() {
	doc, err := paper.FromHTML(`<h1>Hello</h1><p>World</p>`)
	if err != nil {
		log.Fatal(err)
	}

	_ = doc.GetBytes()
}

func FromHTMLCtx

func FromHTMLCtx(ctx context.Context, htmlStr string, cfgs ...*entity.Config) (*core.Pdf, error)

FromHTMLCtx converts an HTML string directly into a PDF document. It observes ctx while parsing/translating HTML and while generating the PDF.

func FromHTMLReader

func FromHTMLReader(r io.Reader, cfgs ...*entity.Config) (*core.Pdf, error)

FromHTMLReader reads HTML from r and converts it directly into a PDF document. Optional configs are the same configs accepted by New.

func FromHTMLReaderCtx

func FromHTMLReaderCtx(ctx context.Context, r io.Reader, cfgs ...*entity.Config) (*core.Pdf, error)

FromHTMLReaderCtx reads HTML from r and converts it directly into a PDF document. It observes ctx before and after reading, while translating HTML, and while generating the PDF.

Types

type Paper

type Paper struct {
	// contains filtered or unexported fields
}

func New

func New(cfgs ...*entity.Config) *Paper

New creates a concrete Paper instance. It's optional to provide an *entity.Config with customizations those customization are created by using the config.Builder.

Example

ExampleNew demonstrates how to create a paper instance.

package main

import (
	"github.com/avdoseferovic/paper"
	"github.com/avdoseferovic/paper/pkg/config"
)

func main() {
	// optional
	b := config.NewBuilder()
	cfg := b.Build()

	m := paper.New(cfg) // cfg is an optional

	// Do things and generate
	_, _ = m.Generate()
}

func NewPaper

func NewPaper(cfgs ...*entity.Config) *Paper

NewPaper creates a concrete Paper instance.

func (*Paper) AddAutoRow

func (m *Paper) AddAutoRow(cols ...core.Col) core.Row

AddAutoRow is responsible for adding a line with automatic height to the current document. The row height will be calculated based on its content.

func (*Paper) AddHTML

func (m *Paper) AddHTML(htmlStr string) error

AddHTML parses an HTML string into Paper rows and adds them to the current document. Headers, footers, and pagination continue to work as with manually constructed rows. For advanced options (e.g. html.WithImageBaseDir for safe <img> loading), call html.FromString directly and append the returned rows via m.AddRows(rows...). Supported HTML subset is documented in docs/html-support.md.

func (*Paper) AddHTMLCtx

func (m *Paper) AddHTMLCtx(ctx context.Context, htmlStr string) error

AddHTMLCtx parses an HTML string into Paper rows and adds them to the current document. It observes ctx while parsing and translating the HTML.

func (*Paper) AddPages

func (m *Paper) AddPages(pages ...core.Page)

AddPages is responsible for add pages directly in the document. By adding a page directly, the current cursor will reset and the new page will appear as the next. If the page provided have more rows than the maximum useful area of a page, paper will split that page in more than one.

Example

ExamplePaper_AddPages demonstrates how to add a new page in paper.

package main

import (
	"github.com/avdoseferovic/paper"
	"github.com/avdoseferovic/paper/pkg/components/code"
	"github.com/avdoseferovic/paper/pkg/components/page"
)

func main() {
	m := paper.New()

	p := page.New()
	p.Add(code.NewBarRow(10, "barcode"))

	m.AddPages(p)

	// Do things and generate
}

func (*Paper) AddRow

func (m *Paper) AddRow(rowHeight float64, cols ...core.Col) core.Row

AddRow is responsible for add one row in the current document. By adding a row, if the row will extrapolate the useful area of a page, paper will automatically add a new page. Paper use the information of PageSize, PageMargin, FooterSize and HeaderSize to calculate the useful area of a page.

Example

ExamplePaper_AddRow demonstrates how to add a new row in paper.

package main

import (
	"github.com/avdoseferovic/paper/pkg/components/text"

	"github.com/avdoseferovic/paper"
)

func main() {
	m := paper.New()

	m.AddRow(10, text.NewCol(12, "text"))

	// Do things and generate
}

func (*Paper) AddRows

func (m *Paper) AddRows(rows ...core.Row)

AddRows is responsible for add rows in the current document. By adding a row, if the row will extrapolate the useful area of a page, paper will automatically add a new page. Paper use the information of PageSize, PageMargin, FooterSize and HeaderSize to calculate the useful area of a page.

Example

ExamplePaper_AddRows demonstrates how to add new rows in paper.

package main

import (
	"github.com/avdoseferovic/paper/pkg/components/text"

	"github.com/avdoseferovic/paper"
	"github.com/avdoseferovic/paper/pkg/components/code"
)

func main() {
	m := paper.New()

	m.AddRows(
		code.NewBarRow(12, "barcode"),
		text.NewRow(12, "text"),
	)

	// Do things and generate
}

func (*Paper) FitInCurrentPage

func (m *Paper) FitInCurrentPage(heightNewLine float64) bool

FitInCurrentPage reports whether a row of the given height fits in the remaining useful area of the current page.

Example

ExamplePaper_FitInCurrentPage demonstrates how to check if the new line fits on the current page.

package main

import (
	"github.com/avdoseferovic/paper"
)

func main() {
	m := paper.New()

	m.FitInCurrentPage(12)

	// Do things and generate
}

func (*Paper) Generate

func (m *Paper) Generate() (*core.Pdf, error)

Generate is responsible to compute the component tree created by the usage of all other Paper methods, and generate the PDF document.

Example

ExamplePaper_Generate demonstrates how to generate a file.

package main

import (
	"log"

	"github.com/avdoseferovic/paper"
)

func main() {
	m := paper.New()

	// Add rows, pages and etc.

	doc, err := m.Generate()
	if err != nil {
		log.Fatal(err)
	}

	// You can retrieve as Base64, Save file, Merge with another file or GetReport.
	_ = doc.GetBytes()
}

func (*Paper) GenerateCtx

func (m *Paper) GenerateCtx(ctx context.Context) (*core.Pdf, error)

GenerateCtx is responsible to compute the component tree created by the usage of all other Paper methods, and generate the PDF document.

func (*Paper) GetCurrentConfig

func (m *Paper) GetCurrentConfig() *entity.Config

GetCurrentConfig is responsible for returning the current settings from the file

Example

ExamplePaper_GetCurrentConfig demonstrates how to get the current paper configuration.

package main

import (
	"github.com/avdoseferovic/paper"
)

func main() {
	m := paper.New()

	m.GetCurrentConfig()

	// Do things and generate
}

func (*Paper) GetStructure

func (m *Paper) GetStructure() *node.Node[core.Structure]

GetStructure is responsible for return the component tree, this is useful on unit tests cases.

Example

ExamplePaperGetStruct demonstrates how to get paper component tree

package main

import (
	"github.com/avdoseferovic/paper/pkg/components/text"

	"github.com/avdoseferovic/paper"
)

func main() {
	m := paper.New()

	m.AddRow(40, text.NewCol(12, "text"))

	m.GetStructure()

	// Do things and generate
}

func (*Paper) RegisterFooter

func (m *Paper) RegisterFooter(rows ...core.Row) error

RegisterFooter is responsible to define a set of rows as a footer of the document. The footer will appear in every new page of the document. The footer cannot occupy an area greater than the useful area of the page, it this case the method will return an error.

Example

ExamplePaper_RegisterFooter demonstrates how to register a footer to me added in every new page. An error is returned if the area occupied by the footer is greater than the page area.

package main

import (
	"github.com/avdoseferovic/paper/pkg/components/text"

	"github.com/avdoseferovic/paper"
	"github.com/avdoseferovic/paper/pkg/components/code"
)

func main() {
	m := paper.New()

	err := m.RegisterFooter(
		code.NewBarRow(12, "barcode"),
		text.NewRow(12, "text"))
	if err != nil {
		panic(err)
	}

	// Do things and generate
}

func (*Paper) RegisterHeader

func (m *Paper) RegisterHeader(rows ...core.Row) error

RegisterHeader is responsible to define a set of rows as a header of the document. The header will appear in every new page of the document. The header cannot occupy an area greater than the useful area of the page, it this case the method will return an error.

Example

ExamplePaper_RegisterHeader demonstrates how to register a header to me added in every new page. An error is returned if the area occupied by the header is greater than the page area.

package main

import (
	"github.com/avdoseferovic/paper/pkg/components/text"

	"github.com/avdoseferovic/paper"
	"github.com/avdoseferovic/paper/pkg/components/code"
)

func main() {
	m := paper.New()

	err := m.RegisterHeader(
		code.NewBarRow(12, "barcode"),
		text.NewRow(12, "text"))
	if err != nil {
		panic(err)
	}

	// Do things and generate
}

Directories

Path Synopsis
docs module
examples module
internal
assert
Package assert provides lightweight test assertions used by the root module.
Package assert provides lightweight test assertions used by the root module.
goleak
Package goleak provides a small goroutine leak check for this repository's tests.
Package goleak provides a small goroutine leak check for this repository's tests.
mocktest
Package mock provides the small mock API subset used by generated internal test mocks.
Package mock provides the small mock API subset used by generated internal test mocks.
pdf
Package pdf contains Paper's internal PDF runtime.
Package pdf contains Paper's internal PDF runtime.
providers
Package providers contains repository-wide provider guard tests.
Package providers contains repository-wide provider guard tests.
require
Package require provides fail-fast test assertions used by the root module.
Package require provides fail-fast test assertions used by the root module.
svg
Package svg provides in-process SVG rasterisation helpers for PDF image embedding.
Package svg provides in-process SVG rasterisation helpers for PDF image embedding.
pkg
components/checkbox
Package checkbox implements creation of checkboxes.
Package checkbox implements creation of checkboxes.
components/code
Package code implements creation of Barcode, MatrixCode and QrCode.
Package code implements creation of Barcode, MatrixCode and QrCode.
components/col
Package col implements creation of columns.
Package col implements creation of columns.
components/html
Package html implements a component wrapper for Paper's HTML translator.
Package html implements a component wrapper for Paper's HTML translator.
components/htmllist
Package htmllist implements HTML-style bullet and numbered list components.
Package htmllist implements HTML-style bullet and numbered list components.
components/image
Package image implements creation of images from file and bytes.
Package image implements creation of images from file and bytes.
components/line
Package line implements creation of lines.
Package line implements creation of lines.
components/list
Package list implements creation of lists (old tablelist).
Package list implements creation of lists (old tablelist).
components/page
Package page implements creation of pages.
Package page implements creation of pages.
components/richtext
Package richtext implements a PDF component for paragraphs with mixed inline styling.
Package richtext implements a PDF component for paragraphs with mixed inline styling.
components/row
Package row implements creation of rows.
Package row implements creation of rows.
components/signature
Package signature implements creation of signatures.
Package signature implements creation of signatures.
components/table
Package table implements a PDF table component with colspan, rowspan, and per-cell styling.
Package table implements a PDF table component with colspan, rowspan, and per-cell styling.
components/text
Package text implements creation of texts.
Package text implements creation of texts.
config
Package config implements custom configuration builder.
Package config implements custom configuration builder.
consts/align
Package align contains all align types.
Package align contains all align types.
consts/border
Package border contains all border types.
Package border contains all border types.
consts/breakline
Package breakline contains all break line strategies.
Package breakline contains all break line strategies.
consts/extension
Package extension contains all image extensions.
Package extension contains all image extensions.
consts/fontfamily
Package fontfamily contains all default font families.
Package fontfamily contains all default font families.
consts/fontstyle
Package fontstyle contains all default font styles.
Package fontstyle contains all default font styles.
consts/linestyle
Package linestyle contains all line styles.
Package linestyle contains all line styles.
consts/orientation
Package orientation contains all orientations.
Package orientation contains all orientations.
consts/pagesize
Package pagesize contains all default page sizes.
Package pagesize contains all default page sizes.
consts/protection
Package protection contains all protection types.
Package protection contains all protection types.
consts/provider
Package provider contains all document generation providers.
Package provider contains all document generation providers.
core
Package core contains all core interfaces and basic implementations.
Package core contains all core interfaces and basic implementations.
core/entity
Package entity contains all core entities.
Package entity contains all core entities.
decorator
Package decorator provides decorators over the core.Paper interface.
Package decorator provides decorators over the core.Paper interface.
fontrepository
Package fontrepository implements font repository.
Package fontrepository implements font repository.
html
Package html converts HTML strings into Paper rows so they can be added to a Paper document.
Package html converts HTML strings into Paper rows so they can be added to a Paper document.
html/dom
Package dom provides a Paper-friendly wrapper over golang.org/x/net/html.
Package dom provides a Paper-friendly wrapper over golang.org/x/net/html.
html/translate
Package translate — flex layout dispatch and item-content construction.
Package translate — flex layout dispatch and item-content construction.
merge
Package merge implements PDF merge.
Package merge implements PDF merge.
metrics
Package metrics contains metrics models, constants and formatting.
Package metrics contains metrics models, constants and formatting.
props
Package props contain the public properties of paper.
Package props contain the public properties of paper.
tree/node
Package node provides a small generic tree node used to describe Paper document structure.
Package node provides a small generic tree node used to describe Paper document structure.
test module

Jump to

Keyboard shortcuts

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