Help us shape the future of Go! Take the 2026 Go Developer Survey

pdf

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 2026 License: MIT Imports: 24 Imported by: 0

README

fyne-pdf

CI Coverage Go Reference

A PDF viewer widget for Fyne, written in pure Go.

Pages are rendered with cera — no MuPDF, no cgo PDF library, no external binaries.

Features

  • Document widget: page view with thumbnail strip, zoom slider, zoom in/out, 100 % (physical size), fit page / fit width / fit height
  • Viewer gestures: Ctrl+wheel zoom around the pointer, page turns when scrolling past a page's edge, drag to pan, double tap for fit/100 %; keyboard: Page Up/Down, Space, Home/End, arrow keys, Ctrl +/-/0
  • Rendering in the background, the UI never waits for a page; the resolution of the main view follows the zoom level and the screen scale
  • Optional save button via SaveCallback, render errors via OnError
  • Source: goroutine-safe access to an opened PDF — page count, page bounds, rendering at arbitrary DPI, document metadata
  • NewImageFromMemory: render a single page to an image.Image without any UI
  • Render timeout per page (15 s); slow pages fall back to the partially drawn image instead of blocking the UI

Installation

go get github.com/timzifer/fyne-pdf

Requires Go 1.26+ and Fyne 2.6+ (uses fyne.Do). Fyne itself needs a C compiler and the platform graphics headers — see the Fyne prerequisites.

Usage

Viewer widget
package main

import (
	"os"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	pdf "github.com/timzifer/fyne-pdf"
)

func main() {
	a := app.New()
	w := a.NewWindow("PDF")

	contents, err := os.ReadFile("example.pdf")
	if err != nil {
		panic(err)
	}

	doc := pdf.NewDocument()
	defer doc.Close()
	if err := doc.LoadFromMemory(contents); err != nil {
		panic(err)
	}

	// doc.ShowPage(2) // jump to the third page

	w.SetContent(doc)
	w.Resize(fyne.NewSize(1024, 768))
	w.ShowAndRun()
}

A complete viewer with an open dialog, save button and error reporting is in examples/viewer:

go run ./examples/viewer [file.pdf]
Render a page to an image
img, err := pdf.NewImageFromMemory(contents, 0) // page index is 0-based
Low-level access
src, err := pdf.OpenSource(contents)
if err != nil {
	return err
}
defer src.Close()

fmt.Println(src.PageCount(), src.Metadata()["title"])
img, err := src.RenderPage(0, 300) // 300 dpi

Status

Usable, but young. The API may still change before v1.0.0. Not supported yet: text selection, search, links, annotations, scrolling through all pages continuously.

Contributing

Bug reports and pull requests are welcome — see CONTRIBUTING.md. For rendering bugs, please attach the PDF (or a minimal one reproducing the issue) if you can share it.

License

MIT

Documentation

Overview

Package pdf provides a PDF viewer widget for Fyne, written in pure Go.

Document is a ready-to-use viewer with a thumbnail strip and zoom controls. Source gives goroutine-safe low-level access to an opened PDF (page count, bounds, rendering, metadata), and NewImageFromMemory renders a single page without any UI.

Page indices are 0-based throughout the package.

Index

Examples

Constants

View Source
const ThumbnailMinimumDimension = float32(100)

ThumbnailMinimumDimension is the default width and height of a thumbnail.

Variables

This section is empty.

Functions

func NewImageFromMemory

func NewImageFromMemory(contents []byte, pageNumber int, documentCallbacks ...DocumentCallbackFn) (image.Image, error)

NewImageFromMemory renders page pageNumber (0-based) of the PDF in contents at 144 dpi. documentCallbacks are called with the opened Source before rendering.

Example
package main

import (
	"fmt"
	"os"

	pdf "github.com/timzifer/fyne-pdf"
)

func main() {
	contents, err := os.ReadFile("example.pdf")
	if err != nil {
		panic(err)
	}

	img, err := pdf.NewImageFromMemory(contents, 0)
	if err != nil {
		panic(err)
	}
	fmt.Println(img.Bounds().Size())
}

Types

type Document

type Document struct {
	widget.BaseWidget

	// BackgroundColor fills the area behind the page.
	BackgroundColor color.Color
	// SaveCallback, if set, shows a save button in the toolbar that calls
	// it. Call Refresh after changing it.
	SaveCallback func()
	// OnError is called on the main goroutine when a page cannot be
	// rendered. If nil, the error is logged.
	OnError func(page int, err error)
	// contains filtered or unexported fields
}

Document is a PDF viewer widget: a page view with a toolbar for zooming and a thumbnail strip for navigation. Create it with NewDocument.

The page view supports the usual viewer gestures: Ctrl+wheel (Cmd on macOS) zooms around the pointer, scrolling on past the top or bottom of a page turns it, dragging pans, a double tap switches between the fitted page and 100 %. Once clicked, it takes the keyboard focus: Page Up/Down and Space scroll by a screen, Home/End go to the first and last page, the arrow keys scroll (Left/Right turn pages while the page fits horizontally), Ctrl +/-/0 zoom.

Pages are rendered in the background; the resolution of the main view follows the zoom level. Methods must be called from the Fyne main goroutine (or before the app runs).

func NewDocument

func NewDocument() *Document

NewDocument creates an empty viewer. Load a PDF with Document.LoadFromMemory.

func (*Document) Close

func (d *Document) Close() error

Close releases the loaded PDF and stops pending renders. Pages already shown stay visible.

func (*Document) CreateRenderer

func (d *Document) CreateRenderer() fyne.WidgetRenderer

func (*Document) CurrentPage

func (d *Document) CurrentPage() int

CurrentPage returns the index (0-based) of the page shown in the main view.

func (*Document) LoadFromMemory

func (d *Document) LoadFromMemory(contents []byte) error

LoadFromMemory opens the PDF in contents and shows its first page. A previously loaded PDF is closed. Pages are rendered in the background.

func (*Document) PageCount

func (d *Document) PageCount() int

PageCount returns the number of pages of the loaded PDF.

func (*Document) Refresh

func (d *Document) Refresh()

func (*Document) Resize

func (d *Document) Resize(size fyne.Size)

Resize lays out the widget. While the page is fitted to the view (after ShowPage or one of the ZoomToFit methods), the fit follows the new size.

func (*Document) ShowPage

func (d *Document) ShowPage(page int)

ShowPage shows page (0-based) in the main view and zooms it to fit.

func (*Document) Zoom

func (d *Document) Zoom(scale float64)

Zoom sets the zoom factor; 1 shows the page at roughly its physical size. The factor is clamped to 0.2 to 4.

func (*Document) ZoomToFit

func (d *Document) ZoomToFit()

ZoomToFit scales the page so it fits completely into the view. The fit is kept when the view is resized, until the zoom is changed otherwise; the same holds for ZoomToFitVertical and ZoomToFitHorizontal.

func (*Document) ZoomToFitHorizontal

func (d *Document) ZoomToFitHorizontal()

ZoomToFitHorizontal scales the page to the width of the view.

func (*Document) ZoomToFitVertical

func (d *Document) ZoomToFitVertical()

ZoomToFitVertical scales the page to the height of the view.

type DocumentCallbackFn

type DocumentCallbackFn func(src *Source)

DocumentCallbackFn is called with the opened Source before rendering, see NewImageFromMemory.

type Page

type Page struct {
	widget.BaseWidget

	Scale           float64
	BackgroundColor color.Color
	// contains filtered or unexported fields
}

Page displays a single rendered page image at Page.PageSize multiplied by Scale, which is also its minimum size. A larger widget centres the page on BackgroundColor. The image is scaled to the page size, so it can be rendered at any resolution; the aspect ratio is kept.

func NewPage

func NewPage() *Page

NewPage creates an empty Page.

func NewPageWithImage

func NewPageWithImage(img image.Image) *Page

NewPageWithImage creates a Page showing img.

func (*Page) CreateRenderer

func (p *Page) CreateRenderer() fyne.WidgetRenderer

func (*Page) PageSize

func (p *Page) PageSize() fyne.Size

PageSize returns the size of the page at Scale 1. Unless set with Page.SetPageSize, this is the size of the page image in pixels.

func (*Page) SetImage

func (p *Page) SetImage(pageImage image.Image)

SetImage replaces the displayed page image.

func (*Page) SetPageSize

func (p *Page) SetPageSize(size fyne.Size)

SetPageSize sets the size of the page at Scale 1, independent of the resolution of the image.

type Source

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

Source is an opened PDF. All methods are safe for concurrent use: cera only allows interpreting pages of a document from one goroutine at a time, hence the mutex. A single page is still rasterized by cera on all cores.

func OpenSource

func OpenSource(contents []byte) (*Source, error)

OpenSource parses the PDF in contents.

Example
package main

import (
	"fmt"
	"os"

	pdf "github.com/timzifer/fyne-pdf"
)

func main() {
	contents, err := os.ReadFile("example.pdf")
	if err != nil {
		panic(err)
	}

	src, err := pdf.OpenSource(contents)
	if err != nil {
		panic(err)
	}
	defer func() { _ = src.Close() }()

	fmt.Println("pages:", src.PageCount())
	fmt.Println("title:", src.Metadata()["title"])

	img, err := src.RenderPage(0, 300)
	if err != nil {
		panic(err)
	}
	fmt.Println("size:", img.Bounds().Size())
}

func (*Source) Bound

func (s *Source) Bound(page int) (image.Rectangle, error)

Bound returns the visible area of page (0-based) in points (72 dpi), with /Rotate and /UserUnit already applied.

func (*Source) Close

func (s *Source) Close() error

Close releases the document. Afterwards all methods return an error or zero pages. Closing more than once is allowed.

func (*Source) Metadata

func (s *Source) Metadata() map[string]string

Metadata returns the entries of the document info dictionary with lowercase keys ("title", "author", ...).

func (*Source) PageCount

func (s *Source) PageCount() int

PageCount returns the number of pages, or 0 after Close.

func (*Source) RenderPage

func (s *Source) RenderPage(page int, dpi float64) (image.Image, error)

RenderPage renders page (0-based) at the given resolution onto a white background. If rendering takes longer than 15 seconds, the partially drawn image is returned without an error.

func (*Source) RenderPageContext

func (s *Source) RenderPageContext(ctx context.Context, page int, dpi float64) (image.Image, error)

RenderPageContext is like Source.RenderPage, but stops early and returns ctx.Err() when ctx is cancelled.

type Thumbnail

type Thumbnail struct {
	widget.BaseWidget

	OnTapped func()
	// contains filtered or unexported fields
}

Thumbnail is a tappable page preview with a title below it.

func NewThumbnail

func NewThumbnail() *Thumbnail

NewThumbnail creates an empty Thumbnail.

func (*Thumbnail) CreateRenderer

func (t *Thumbnail) CreateRenderer() fyne.WidgetRenderer

func (*Thumbnail) Selected

func (t *Thumbnail) Selected() bool

Selected reports whether the thumbnail is highlighted.

func (*Thumbnail) SetImage

func (t *Thumbnail) SetImage(theImage image.Image)

SetImage sets the preview image.

func (*Thumbnail) SetMinimumSize

func (t *Thumbnail) SetMinimumSize(size fyne.Size)

SetMinimumSize sets the size of the preview image area.

func (*Thumbnail) SetSelected

func (t *Thumbnail) SetSelected(selected bool)

SetSelected highlights the thumbnail, e.g. for the page currently shown.

func (*Thumbnail) SetTitle

func (t *Thumbnail) SetTitle(title string)

SetTitle sets the label shown below the preview.

func (*Thumbnail) Tapped

func (t *Thumbnail) Tapped(_ *fyne.PointEvent)

Directories

Path Synopsis
examples
viewer command
Command viewer is a minimal PDF viewer built on fyne-pdf.
Command viewer is a minimal PDF viewer built on fyne-pdf.

Jump to

Keyboard shortcuts

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