From 50d3df3865ec45d67d7f8e3d862842a8e947f477 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Thu, 3 Sep 2026 23:59:13 +0200 Subject: [PATCH] pdfkit: add Options.Subject and Keywords for the info dictionary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The document information dictionary held only Title and Author. Subject (a one-line description) and Keywords (search terms) are the other two standard text fields a viewer's document properties and reference managers read. Both are written as PDF text strings, so a non-ASCII value is encoded UTF-16BE like Title/Author. Verified: Subject/Keywords appear in the info dictionary; an accented value is UTF-16BE (), not raw UTF-8. 100% coverage; go vet and gofmt clean. Co-Authored-By: Claude Opus 4.8 --- document.go | 22 +++++++++++++++++----- subjectkeywords_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 subjectkeywords_test.go diff --git a/document.go b/document.go index c8b4b6d..eaeaf35 100644 --- a/document.go +++ b/document.go @@ -17,10 +17,15 @@ import ( // Options configures a Document. The zero value is valid and yields a // deterministic, uncompressed document with no timestamps. type Options struct { - // Title and Author populate the document information dictionary. Empty - // values are omitted. - Title string - Author string + // Title, Author, Subject and Keywords populate the document information + // dictionary. Empty values are omitted. Subject is a one-line description of + // the document; Keywords is a list of search terms (conventionally + // comma-separated). Each is written as a PDF text string, so a non-ASCII value + // is encoded UTF-16BE. + Title string + Author string + Subject string + Keywords string // Producer is the /Producer string in the information dictionary. When // empty it defaults to DefaultProducer. @@ -215,7 +220,8 @@ func (d *Document) Write(w io.Writer) error { bd.put(catalog, catDict) var info objRef - hasInfo := d.opts.Title != "" || d.opts.Author != "" || d.producer() != "" || d.opts.Now != nil + hasInfo := d.opts.Title != "" || d.opts.Author != "" || d.opts.Subject != "" || + d.opts.Keywords != "" || d.producer() != "" || d.opts.Now != nil if hasInfo { info = bd.add(d.infoDict()) } @@ -399,6 +405,12 @@ func (d *Document) infoDict() *pdfDict { if d.opts.Author != "" { info.set("Author", pdfTextString(d.opts.Author)) } + if d.opts.Subject != "" { + info.set("Subject", pdfTextString(d.opts.Subject)) + } + if d.opts.Keywords != "" { + info.set("Keywords", pdfTextString(d.opts.Keywords)) + } if p := d.producer(); p != "" { info.set("Producer", pdfString(p)) } diff --git a/subjectkeywords_test.go b/subjectkeywords_test.go new file mode 100644 index 0000000..233bcc0 --- /dev/null +++ b/subjectkeywords_test.go @@ -0,0 +1,40 @@ +// Copyright (c) the go-pdfkit/pdfkit authors. +// SPDX-License-Identifier: BSD-3-Clause + +package pdfkit + +import ( + "bytes" + "testing" +) + +// Subject and Keywords reach the information dictionary as text strings. +func TestInfoSubjectAndKeywords(t *testing.T) { + doc := New(Options{Subject: "A study of widgets", Keywords: "widgets, gadgets, gizmos"}) + doc.AddPage(A4) + var b bytes.Buffer + if err := doc.Write(&b); err != nil { + t.Fatal(err) + } + for _, want := range []string{"/Subject (A study of widgets)", "/Keywords (widgets, gadgets, gizmos)"} { + if !bytes.Contains(b.Bytes(), []byte(want)) { + t.Errorf("output missing %q\n%s", want, b.String()) + } + } +} + +// A non-ASCII Subject/Keywords value is encoded UTF-16BE, like Title/Author. +func TestInfoSubjectKeywordsAccentedUTF16(t *testing.T) { + doc := New(Options{Subject: "Résumé", Keywords: "clé, référence"}) + doc.AddPage(A4) + var b bytes.Buffer + if err := doc.Write(&b); err != nil { + t.Fatal(err) + } + if !bytes.Contains(b.Bytes(), []byte("