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

render

package module
v0.65.0 Latest Latest
Warning

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

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

README

render

CI Go Reference Go Report Card Coverage

Turns a page of a PDF into pixels, in pure Go with no C anywhere.

It reads with reader, takes what a document says about its fonts from pdffont, rasterises with go-gfx/gfx, and — where there is text — shapes and outlines it with go-opentype. Nothing else: this builds for GOOS=js/wasm and every architecture the fleet targets, which is what lets a PDF be shown in a browser tab with nothing on the far end.

What it draws

The graphics half of the format: the transform stack, path construction and every one of the ten painting operators, filling under both winding rules, stroking with the caps, joins, miter limit and dash pattern the graphics state carries, clipping — including clips that intersect and are let go again with Q — the device, calibrated, profile-based, indexed, separation and pattern colour spaces, constant transparency, and form XObjects with their own matrix, bounding box and resources.

Images too: XObjects and inline images, at every bit depth the format has, in every colour space, with a /Decode array, as one-bit stencils painted in the colour in force, and with either kind of transparency — a soft mask of levels or a stencil of what to leave out. A JPEG is decoded by the standard library; a format nothing here reads is left undrawn rather than drawn wrong.

Text: the whole text state and every positioning and showing operator, with glyphs taken from an embedded TrueType or OpenType font, from a PostScript Type 1 program, from a bare CFF one, from a composite font addressed by character identifier, or — for a Type 3 font, whose glyphs are little content streams — by running them. A simple font is addressed by the name its encoding gives a code, and falls back on the program's own built-in encoding when the document says nothing. A font whose program the file does not carry at all — two pages of the corpus in five — is drawn in a stand-in: Arimo, Tinos and Cousine are metric-compatible with Helvetica, Times and Courier, so the text comes out the width it was meant to be even where the document gives no widths of its own. A bold is made by stroking the outline as well as filling it and an italic by leaning it over, since only one weight of each stand-in is carried. Symbol and the dingbats are left alone: no face here has their glyphs, and something is not better than nothing when the something is the wrong alphabet.

Gradients and patterns: the axial and radial shadings that carry nearly every gradient anyone draws, painted on their own with sh or used as a fill or a stroke; tiling patterns, coloured and uncoloured, run once per cell and clipped to the shape; and the four kinds of function the format has — a sampled grid, an exponential curve, several of them stitched end to end, and a little PostScript program — which are also what a Separation or DeviceN colour space's tint transform is written in.

And the mesh shadings, all four of them, which is how a plotting tool writes a surface: free-form and lattice-form triangles, each corner its own colour, and Coons and tensor patches, whose four sides are curves. A patch is drawn by cutting it into a grid of little quadrilaterals — the inside of a Coons patch follows from its twelve boundary points, and a tensor patch says four more — and every triangle is filled by mixing the colours at its corners across it. A patch may carry on from the one before it, sharing an edge and two of its colours, which is how a surface is written without repeating a single point.

A pattern is placed in the space of the content that names it. That is the page's own space at the top level, and inside a form it is the form's: its matrix and the transform that drew it both count. Nearly every figure a plotting tool writes is a form, so a pattern that stayed at the page's origin would miss the shape it was asked to fill by the width of a page.

How it is checked

By writing a PDF whose geometry is known and looking at the pixels that come out: a rectangle from (10,10) to (40,40) on a hundred-point page has to be ink at (25,75) in the image and paper at (25,55), which is what says the page's coordinates — which count up from the bottom left — were mapped the right way up. Rotation, a media box that does not start at the origin, the crop box winning over the media box, every colour space, both winding rules, clipping, transparency and forms are all checked the same way.

Then on the corpus, for robustness rather than correctness: 4 108 pages drawn from 3 999 real files with no panics and no failures. 6 of them come out blank, which is the honest count of pages carrying nothing this wave understands yet — it was 373 before images, 31 before text, and 26 before Type 1 and bare CFF font programs.

And, when what is underneath changes, on the corpus again — by hashing every page's pixels before and after rather than by watching the exit status. That is how the two defects behind gfx v0.10.0 and opentype v0.7.0 were measured: a stroke assembled out of overlapping pieces came out at about half the colour it was asked for, and a CID font that carried no character map was refused outright, taking every glyph in it off the page. 3 520 of 4 057 pages changed; 1 705 of them gained ink, one of them twice over.

Testing

go test -covermode=set ./...

CI gates on exact 100% statement coverage, go vet, and a cross-compile across linux/{amd64,arm64,riscv64,loong64,ppc64le,s390x}, js/wasm, darwin/arm64 and windows/amd64.

The rest of the family

reader reads and writes the format · ops is the verbs and the pdfops command · render turns a page into pixels · pdffont is what a document says about a font · extract reads a page back · coedit is a PDF several people edit at once · app is the browser workbench.

Documentation for all of it: https://go-pdfkit.github.io/docs/

License

BSD-3-Clause — see LICENSE. Copyright the go-pdfkit/render authors.

Documentation

Overview

Package render turns a page of a PDF into pixels.

It reads with github.com/go-pdfkit/reader and rasterises with github.com/go-gfx/gfx. Nothing outside those and the standard library is used, so it builds for js/wasm and for every architecture the fleet targets — which is what lets a PDF be shown in a browser tab with nothing on the far end.

Index

Constants

This section is empty.

Variables

View Source
var ErrTimedOut = errors.New("render: the page was still being drawn when its time ran out")

ErrTimedOut says a page was still being drawn when its time ran out. The image returned with it holds what had been drawn by then.

View Source
var ErrTooMuchToDecode = errors.New("render: the page names more picture than may be decoded at once")

ErrTooMuchToDecode says a page names more picture than Images will decode at once. Nothing comes back with it: half the pictures of a page would be read as the whole of them by anything counting.

Functions

func Page

func Page(d *reader.Document, i int, opt Options) (*raster.Image, error)

Page draws the i'th page of a document, counting from one.

Types

type Image added in v0.17.0

type Image struct {
	// Name is the resource name the page draws it by.
	Name string
	// Object is the number of the indirect object the picture was stored in,
	// and nought when it has none: an inline image, or a stream written
	// directly into the resource dictionary rather than referred to.
	//
	// It is here because it is the one identity a picture and another tool's
	// extraction of it can be matched on, and this is the only place that
	// knows it without guessing. A caller left with the NAME has to walk the
	// document again to recover it, and a name is not an identity: it is
	// unique within one resource dictionary and not across the several a page
	// reaches through its forms, so `Im1` can mean two different objects on
	// one page. go-pdfkit/conformance rebuilt this number that way and needed
	// three mechanisms to do it -- a walk, an ambiguity rule for names that
	// reached two objects, and a mapping from a mask to the picture that names
	// it -- and each of the three produced a defect.
	//
	// A MASK carries the object of the picture that NAMES it, not its own.
	// That is what pdfimages publishes on an smask row, and it is the number a
	// mask can actually be paired on.
	Object int
	// Filter is the image format the picture was stored in — DCTDecode,
	// JPXDecode, JBIG2Decode, CCITTFaxDecode — and is empty when the bytes
	// were samples that no image codec had to read.
	Filter string
	// Decoded says the dictionary carried a /Decode array, which maps the
	// stored samples onto the range the colour space wants and is applied
	// here. It is worth knowing about because it is the one thing that makes
	// this picture differ from the same picture as another tool EXTRACTS it:
	// pdfimages writes the samples as stored, so a /Decode of [1 0] on a
	// one-bit mask makes the two exact complements of each other, and every
	// pixel differs for a reason that is not a disagreement.
	Decoded bool
	// Stencil says the picture is a one-bit mask, which carries no colours of
	// its own: it paints whatever colour is in force through its own shape.
	// Its Pic has that shape in the alpha channel and black everywhere else,
	// because the colour is not the image's to know.
	Stencil bool
	// Pic is the decoded picture.
	Pic *raster.Image
}

Image is one picture a page draws, decoded.

func Images added in v0.17.0

func Images(d *reader.Document, i int) ([]Image, error)

Images decodes the pictures the i'th page draws, counting from one, in the order of the names it draws them by.

It exists because a page is the wrong unit for two jobs. Getting the pictures out of a document is one. Finding out whether a codec is right is the other: a page is a composition, so one image decoded wrongly can be averaged away by everything drawn over and around it, and a per-page difference of a few percent says nothing about which image was wrong. That mattered here — a release went out drawing scanned pages dark because "ink appeared" was measured instead of "the right ink".

What comes back is what each CODEC produced, not what the page draws. A picture that names a mask is returned unmasked, and the mask is returned beside it as its own entry, named for the key that named it. That is what pdfimages does, and it is the only way the two can be compared: applying a mask to one side and not the other made 21 of 22 JPEG 2000 pictures in a corpus of scanned pages look wrong, when 11 of them differed by nothing but the /SMask.

It also means a picture whose mask cannot be read still comes back. Page declines to draw that one, because how much of it shows is unknown — but the codec read it, and this is about the codec.

A picture nothing here can decode is left out rather than returned empty, which is the same answer Page gives by not drawing it. Inline images — the ones written into the content stream with BI — are not returned: they are named by no resource and are objects of nothing, so there is nothing for a tool that extracts a file's objects to hand back beside them.

It is the CONTENT STREAM that is walked, not the page's /Resources. A resource dictionary is a catalogue of what a page may draw, and PDF lets every page in a file share one; see [renderer.imagesDrawn] for what walking the catalogue instead did to a measurement.

Each picture comes back once, however many of the page's forms reach it, and a page that draws more picture than [maxImagesPixels] is refused whole with ErrTooMuchToDecode rather than decoded until the machine gives out.

type Options

type Options struct {
	// Scale is device pixels per point. Zero means one, which is seventy-two
	// to the inch; [Options.DPI] is the same thing said the other way.
	Scale float64
	// DPI sets the scale from dots per inch. It is ignored when Scale is set.
	DPI float64
	// Background is painted before anything else. The zero value is opaque
	// white, which is what paper is.
	Background *color.RGBA
	// MaxPixels refuses a page that would come out larger than this, so a
	// media box a file made up cannot be asked to fill memory. Zero means
	// forty million, which is a little over A4 at 600 dots to the inch.
	MaxPixels int
	// NoAnnotations leaves out what the page carries beside its content: the
	// notes, the highlights, the stamps, and the fields of a form. They are
	// drawn by default, because a filled-in form drawn without them is a form
	// with nothing in it.
	NoAnnotations bool
	// MaxDuration is how long the page may be drawn for. Zero means as long as
	// it takes, which is what this did before there was anywhere to say
	// otherwise.
	//
	// Some pages take a very long time. Of 59 432 corpus pages drawn at half
	// as much again as their own size, 1 131 were still going after twenty
	// seconds, and one figure took two hundred and seventy-three. A caller
	// drawing somebody else's file — a browser tab, a queue of them, a server
	// — cannot afford to wait for the worst of those and has, until now, had
	// no way to say so: MaxPixels bounds what comes out, and nothing bounded
	// the work of making it.
	//
	// When the time runs out the page stops being drawn and comes back as far
	// as it got, with ErrTimedOut. That is deliberately not a blank: half a
	// page is worth more than nothing to somebody scrolling, and the error
	// says plainly that it is half.
	MaxDuration time.Duration
	// AllLayers draws every optional-content layer, including the ones the
	// document's default configuration turns off.
	//
	// The default is to draw what the document says it shows when it is
	// opened. On the corpus that is the same picture either way: 275 of the
	// 1 633 real forms use optional content and 161 hide a group, but not one
	// mark on any page of any of the 8 300 files measured falls inside a layer
	// its own configuration hides. See optional.go for the numbers and for why
	// the mechanism is read all the same.
	//
	// A caller that wants everything — a tool showing a layer panel, an
	// archive comparing what a file holds against what it displays — sets
	// this and gets the behaviour from before optional content was read.
	AllLayers bool
}

Options say how a page is to be drawn.

Jump to

Keyboard shortcuts

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