Document

PDF Document

The starting point of all backend activity is to create a Document struct. Initialize with an io.Writer

func NewDocument(w io.Writer) *PDFDocument

which gets you a big structure:

type PDFDocument struct {
	CreationDate         time.Time
	ColorProfile         *ColorProfile
	CurrentPage          *Page
	DefaultLanguage      *lang.Lang
	Languages            map[string]*lang.Lang
	PDFWriter            *pdf.PDF
	RoleMap              map[string]string // maps custom roles to standard PDF roles
	RootStructureElement *StructureElement
	ViewerPreferences    map[string]string
	DefaultLanguageTag string // BCP 47 language tag for the document catalog (e.g. "de", "en-US")
	Author             string
	Creator            string
	Filename           string
	Keywords           string
	Subject            string
	Title              string
	Attachments []Attachment
	Faces       []*pdf.Face
	Pages       []*Page
	Spotcolors  []*color.Color
	Bleed             bag.ScaledPoint
	CompressLevel     uint
	DefaultPageHeight bag.ScaledPoint
	DefaultPageWidth  bag.ScaledPoint
	Format            Format // The PDF format (PDF/X-1, PDF/X-3, PDF/A, etc.)
	DumpOutput     bool
	ShowCutmarks   bool
	ShowHyperlinks bool
	SuppressInfo   bool
}

After you are done writing Pages and other material to the PDF, you must end your work with

func (d *PDFDocument) Finish() error

which does everything that is left over, except for closing the PDF file.

PDF metadata

To add metadata, just set the appropriate fields in the main PDFDocument struct:

pd := NewDocument(w)
pd.Author = "A. U. Thor"
pd.Title = "A short story"

The backend fills in the Info dict and adds the required XML metadata to the PDF file.

/Title (A short story)
/Author (A. U. Thor.)

in the Info dict and the XML metadata:

<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <dc:title>A short story</dc:title>
  <dc:creator>A. U. Thor.</dc:creator>
</rdf:Description>

Creating pages

A new page is created with

func (d *PDFDocument) NewPage() *Page

This sets the current page of the document to a new page with the default page height and page width. It also adds this page to the slice of document pages, so that when writing the PDF, you don’t have to do anything special to get the page into the PDF.

type Page struct {
	Userdata map[any]any
	Background        []Object
	Objects           []Object
	StructureElements []*StructureElement
	Annotations       []pdf.Annotation
	Spotcolors        []*color.Color
	Height            bag.ScaledPoint
	Width             bag.ScaledPoint
	ExtraOffset       bag.ScaledPoint
	Objectnumber      pdf.Objectnumber
	Finished bool
}

If you are finished with a page, you call Shipout() to put the page into the PDF.

func (p *Page) Shipout()

Shipout() creates crop marks (if requested), adds bleed amount to the page dimensions, writes all the Objects from the Background and Objects slice to the PDF as well as the Annotations, the Spotcolors and StructureElements.

func (p *Page) OutputAt(x bag.ScaledPoint, y bag.ScaledPoint, vlist *node.VList)
type Object struct {
	Vlist *node.VList
	X     bag.ScaledPoint
	Y     bag.ScaledPoint
}

Page callback

You can register a pre-shipout callback (which is called before the bleed marks are created) with

func (d *PDFDocument) RegisterCallback(cb Callback, fn any)

and register the PreShipoutCallback which must have the signature func(page *Page).

type CallbackShipout func(page *Page)
preShipout := func(pg *document.Page) {
    ...
}
mydoc.RegisterCallback(document.CallbackPreShipout, preShipout)

Images

func (d *PDFDocument) LoadImageFile(filename string) (*pdf.Imagefile, error)

Attachments

You can attach documents to a PDF file:

a := document.Attachment{
	Name:        "test.pdf",
	Description: "A very nice test document",
	MimeType:    "application/pdf",
	Data:        []byte("test"),
}
f.Doc.Attachments = append(f.Doc.Attachments, a)

The possible attributes in the attachment structure are:

type Attachment struct {
	// Creation date of the attachment
	CreationDate time.Time
	// Modified date of the attachment
	ModDate time.Time
	// Name of the attachment (the visible name in the PDF)
	// This is the name of the file as it will be displayed in the PDF viewer
	// and is not necessarily the same as the original file name.
	Name string
	// Description of the attachment
	Description string
	// MimeType of the attachment
	MimeType string
	// Data of the attachment
	Data []byte
}

XMP extension schemas

PDF/A documents only allow a fixed set of XMP metadata namespaces. To use custom namespaces (e.g. for ZUGFeRD/Factur-X e-invoicing), you must declare them as XMP extension schemas. The XMPExtension struct lets you do this without writing raw XML:

type XMPExtension struct {
	Schema       string                 // human-readable schema name
	NamespaceURI string                 // namespace URI
	Prefix       string                 // XML prefix
	Properties   []XMPExtensionProperty // property declarations
	Values       map[string]string      // property name → value
}

Each property in the schema is described by:

type XMPExtensionProperty struct {
	Name        string
	ValueType   string
	Category    string
	Description string
}

Register extensions on the document with:

func (d *PDFDocument) AddXMPExtension(ext XMPExtension)

The backend renders the extension into two XMP blocks automatically: a schema declaration (pdfaExtension:schemas) and a values block with the actual property values.

Example: ZUGFeRD / Factur-X

doc.AddXMPExtension(document.XMPExtension{
    Schema:       "ZUGFeRD PDFA Extension Schema",
    NamespaceURI: "urn:ferd:pdfa:CrossIndustryDocument:invoice:1p0#",
    Prefix:       "zf",
    Properties: []document.XMPExtensionProperty{
        {Name: "DocumentFileName", ValueType: "Text", Category: "external",
            Description: "name of the embedded XML invoice file"},
        {Name: "DocumentType", ValueType: "Text", Category: "external",
            Description: "INVOICE"},
        {Name: "Version", ValueType: "Text", Category: "external",
            Description: "The actual version of the ZUGFeRD data"},
        {Name: "ConformanceLevel", ValueType: "Text", Category: "external",
            Description: "The conformance level of the ZUGFeRD data"},
    },
    Values: map[string]string{
        "ConformanceLevel": "EN 16931",
        "DocumentFileName": "factur-x.xml",
        "DocumentType":     "INVOICE",
        "Version":          "1.0",
    },
})

For a higher-level API that handles ZUGFeRD automatically (format, attachment, and XMP in one call), see the bagme package.

Color profiles

The PDF/A and PDF/X formats require an ICC color profile (OutputIntent). The backend includes two embedded profiles:

func (d *PDFDocument) LoadDefaultColorprofile() (*ColorProfile, error)

Loads the FOGRA39 CMYK profile (ISOcoated_v2_eci.icc). Use this for print workflows with CMYK colors.

func (d *PDFDocument) LoadSRGBColorprofile() (*ColorProfile, error)

Loads the sRGB IEC61966-2.1 profile (sRGB2014.icc). Use this for documents that use RGB colors, for example when converting HTML/CSS to PDF/A-3b.

func (d *PDFDocument) LoadColorprofile(filename string) (*ColorProfile, error)

Loads a custom ICC profile from a file path.

type ColorProfile struct {
	Identifier string
	Registry   string
	Info       string
	Condition  string
	Colors int
}

Accessibility

type StructureElement struct {
	Parent     *StructureElement
	Obj        *pdf.Object
	Role       string
	ActualText string
	Alt        string     // alternative text (for Figure, Formula etc.)
	Lang       string     // BCP 47 language tag for this element
	Scope      string     // for TH: &amp;#34;Row&amp;#34;, &amp;#34;Column&amp;#34;, &amp;#34;Both&amp;#34;
	BBox       [4]float64 // bounding box [x, y, width, height] in PDF points; set during shipout
	HasBBox    bool
	ID int
}
func (se *StructureElement) AddChild(cld *StructureElement)

Debugging

func (d *PDFDocument) SetVTrace(t VTrace)
type VTrace int
func (d *PDFDocument) ClearVTrace(t VTrace)
func (d *PDFDocument) IsTrace(t VTrace) bool
func (d *PDFDocument) OutputXMLDump(w io.Writer) error