Set XMP Metadata
Writing XMP is a round trip: parse the existing packet (or start a new one), set
the models you care about, marshal back to bytes, and store the result as the
catalog’s metadata stream. Document.SetPdfInfo handles the common case, mapping a
document information dictionary into the pdf: namespace so the two systems agree.
Doing it
xmpDoc := xmputil.NewDocument()
pdfInfo, err := reader.GetPdfInfo()
if err != nil {
return err
}
err = xmpDoc.SetPdfInfo(&xmputil.PdfInfoOptions{
InfoDict: pdfInfo.ToPdfObject(),
PdfVersion: reader.PdfVersion().String(),
Copyright: "Copyright Example",
Overwrite: true,
})
if err != nil {
return err
}
metadataBytes, err := xmpDoc.MarshalIndent("", "\t")
if err != nil {
return err
}
metadataStream, err := core.MakeStream(metadataBytes, nil)
if err != nil {
return err
}
return pdfWriter.SetCatalogMetadata(metadataStream)To edit an existing packet instead of replacing it, load it first with
GetCatalogMetadata, core.GetStream and xmputil.LoadDocument, and only fall
back to NewDocument when the catalog has no metadata. Replacing a packet that
was already there throws away every namespace it held, not just pdf:.
Overwrite decides how SetPdfInfo treats the pdf: model that is already in the
packet. With true, the model is cleared first, so the output holds exactly what
you passed. With false, your values are merged over it and anything you left
unset survives - which is what you want when the packet carries fields the info
dictionary does not.
Passing nil as the encoder to core.MakeStream writes the XML uncompressed, so
tools that scan for the XMP packet can find it without decoding the stream.
MarshalIndent is likewise for readability; Marshal produces the same content
without the whitespace.
Limitations
SetPdfInfo reads a fixed set of keys out of InfoDict: Title, Author,
Subject, Keywords, Creator, Producer, Trapped, CreationDate and
ModDate. Custom info keys are ignored - see
set custom XMP metadata for those.
Marked only ever sets. The code applies it when the option is true, so passing
Marked: false leaves an existing pdf:Marked value alone rather than clearing
it. The same goes for PdfVersion and Copyright, where an empty string is
skipped. Use Overwrite: true if you need a field gone.
SetPdfInfo finishes by syncing the pdf: model into the neighbouring
namespaces, which copies title, author, subject and copyright into dc: and the
dates into xmp:. That sync only fills empty targets: an existing dc:title is
left as it was, even with Overwrite: true, because the overwrite applies to the
pdf: model and not to dc:. Set the dc: model directly if the two have to
agree.
A malformed CreationDate or ModDate string makes SetPdfInfo return an error
rather than skipping the field, and Trapped values other than True or False
are recorded as trapped.
SetCatalogMetadata returns an error unless the object is a stream, and passing
nil removes /Metadata from the catalog entirely.
Nothing here updates the document’s own /Info dictionary. If you changed the
values rather than just copying them across, write them back with
PdfWriter.SetDocInfo too.
Run the example
The example copies the input to a writer, loads any existing XMP packet, fills in a
creation date if the input has none, sets the author and modification date, then
writes the pdf: model with Overwrite: true and saves the packet to the output
file.
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/metadata
go run pdf_set_xmp_pdf_metadata.go input.pdf output.pdfIf this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.