How do I use bold and italic together in gpdf?
Bold and italic on the same span work via template.Bold() and template.Italic(). For TrueType fonts you must register all four variants or the BoldItalic lookup silently falls back to the base family.
The question, in other words
I want a single word — or a whole line — to be both bold and italic in the same PDF. How do I set both at once, and why does it sometimes come out looking like neither?
The quick answer
Pass both options on the same c.Text call:
c.Text("WARNING", template.Bold(), template.Italic())
gpdf builds the variant ID Family-BoldItalic and looks it up in the registered fonts. For the Adobe Standard 14 families (Helvetica, Courier, Times) this just works — gpdf aliases -BoldItalic to the canonical -BoldOblique internally and uses the built-in AFM metrics. For a TrueType font you register yourself, you have to register all four variants or the lookup silently falls back to the base family.
That second point is where most of the bugs live.
Working code (Helvetica, no font registration)
package main
import (
"log"
"os"
"github.com/gpdf-dev/gpdf"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/template"
)
func main() {
doc := gpdf.NewDocument(
gpdf.WithPageSize(gpdf.A4),
gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
)
page := doc.AddPage()
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("Regular Helvetica.")
c.Text("Bold only.", template.Bold())
c.Text("Italic only.", template.Italic())
c.Text("Bold and italic.", template.Bold(), template.Italic())
})
})
data, err := doc.Generate()
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile("emphasis.pdf", data, 0o644); err != nil {
log.Fatal(err)
}
}
Four lines, four visible styles. No WithFont calls at all. The PDF ends up referencing Helvetica, Helvetica-Bold, Helvetica-Oblique, and Helvetica-BoldOblique as non-embedded Type 1 entries, which every PDF viewer already has.
What gpdf actually does
The resolver builds a variant ID from the style flags:
Bold() | Italic() | Variant ID looked up |
|---|---|---|
| no | no | Helvetica |
| yes | no | Helvetica-Bold |
| no | yes | Helvetica-Italic → aliased to Helvetica-Oblique |
| yes | yes | Helvetica-BoldItalic → aliased to Helvetica-BoldOblique |
The alias step is the only thing that makes Helvetica special. buildFontVariantID always emits the generic -Italic / -BoldItalic suffixes regardless of family; the Standard 14 init hook then points Helvetica-Italic at Helvetica-Oblique and Helvetica-BoldItalic at Helvetica-BoldOblique so the metrics match what the viewer draws. Courier gets the same treatment. Times doesn't need the alias because its upright form is already called Times-Italic / Times-BoldItalic in the canonical Adobe names.
The trap: TrueType fonts need all four registrations
This is where CJK documents silently break. If you register Noto Sans JP but forget one of the variants, the missing slot does not fall through to Bold or Italic — it falls through straight to the base family.
// Looks fine. Isn't.
doc := gpdf.NewDocument(
gpdf.WithFont("NotoSansJP", regular),
gpdf.WithFont("NotoSansJP-Bold", bold),
gpdf.WithDefaultFont("NotoSansJP", 12),
)
// This renders in plain NotoSansJP — not bold, not italic.
c.Text("強調したい", template.Bold(), template.Italic())
The reason is in the resolver. It tries NotoSansJP-BoldItalic first, misses, then falls back to exactly one thing: the base family NotoSansJP. There's no intermediate step that tries the bold version as a consolation prize. You asked for bold-italic, you got regular.
The fix is to register every variant you intend to use:
package main
import (
"log"
"os"
"github.com/gpdf-dev/gpdf"
"github.com/gpdf-dev/gpdf/document"
"github.com/gpdf-dev/gpdf/template"
)
func main() {
regular := mustRead("NotoSansJP-Regular.ttf")
bold := mustRead("NotoSansJP-Bold.ttf")
italic := mustRead("NotoSansJP-Italic.ttf")
boldItalic := mustRead("NotoSansJP-BoldItalic.ttf")
doc := gpdf.NewDocument(
gpdf.WithPageSize(gpdf.A4),
gpdf.WithFont("NotoSansJP", regular),
gpdf.WithFont("NotoSansJP-Bold", bold),
gpdf.WithFont("NotoSansJP-Italic", italic),
gpdf.WithFont("NotoSansJP-BoldItalic", boldItalic),
gpdf.WithDefaultFont("NotoSansJP", 12),
)
page := doc.AddPage()
page.AutoRow(func(r *template.RowBuilder) {
r.Col(12, func(c *template.ColBuilder) {
c.Text("通常のテキスト")
c.Text("強調", template.Bold(), template.Italic())
})
})
data, _ := doc.Generate()
os.WriteFile("jp-emphasis.pdf", data, 0o644)
}
func mustRead(path string) []byte {
b, err := os.ReadFile(path)
if err != nil { log.Fatal(err) }
return b
}
Noto Sans JP's official distribution doesn't actually ship a slanted italic cut — italic Japanese typography is unusual — so in practice most Japanese documents register only regular and bold and simply don't use template.Italic() on Japanese spans. That's fine. The rule is: if you never call Italic() on a family, you don't need its italic variant. The trap is only when you call Italic() and haven't registered the file.
Mixing bold and italic in the same paragraph
c.Text applies one style to the whole string. For mid-sentence emphasis use c.RichText:
c.RichText(func(rt *template.RichTextBuilder) {
rt.Span("The ")
rt.Span("quick brown fox", template.Bold(), template.Italic())
rt.Span(" jumps over the lazy dog.")
})
Each rt.Span gets its own style flags, and the layout engine line-breaks across spans the way a word processor would. Bold() + Italic() on a single Span hits the same -BoldItalic variant lookup as c.Text — it's the same code path.
One thing worth naming: Bold() and Italic() are commutative. template.Italic(), template.Bold() and template.Bold(), template.Italic() produce identical output. They're setting two different fields (FontWeight and FontStyle) on the same document.Style, so ordering doesn't matter.
Related recipes
- How do I embed a Japanese font in gpdf? — full
WithFontwalkthrough including the four-variant pattern - Why does my PDF show tofu boxes for Japanese? — what "silent fallback" looks like when the base family also isn't registered
- How do I use Noto Sans JP with gpdf? — which Noto files to pick and how
go:embedsimplifies distribution
Try gpdf
gpdf is a Go library for generating PDFs. MIT licensed, zero external dependencies, native CJK support.
go get github.com/gpdf-dev/gpdf