Skip to content

Text & fonts

Loading a font

ttf, _ := os.ReadFile("font.ttf")   // or an OpenType/CFF ('OTTO') font
font, err := pdfkit.LoadFont(ttf)

LoadFont parses a TrueType (glyf) or OpenType/CFF (OTTO) font once. The returned *Font is immutable and may be shared across documents and goroutines — pass the same *Font to SetFont on pages in different documents without reloading. Per-document glyph usage is tracked separately by the Document, so sharing a Font never leaks state between documents.

font.IsCFF()       // true for CFF/OpenType outlines, false for TrueType glyf
font.UnitsPerEm()  // the font's design grid size
font.NumGlyphs()   // glyph count
font.BaseName()    // PostScript name, used for the PDF /BaseFont

Drawing text

p.SetFont(font, 24)                          // Tf — select font and size (points)
p.Text(x, y, "Hello, pdfkit")                // Tj — baseline origin at (x, y)
p.TextLines(x, y, []string{"line one", "line two"}) // consecutive lines

SetFont registers the font with the document for embedding on first use. Text and TextLines return error (a wrapped errNoFont-style error) if no font has been selected yet.

Additional text state, all emitted inside the BT/ET text object:

p.SetCharSpacing(0.5)   // Tc — extra spacing between glyphs, in points
p.SetWordSpacing(1)     // Tw — has no visible effect on Type0 fonts; provided for completeness
p.SetLeading(28)        // TL — baseline-to-baseline distance used by TextLines
p.SetRenderMode(pdfkit.RenderStroke) // Tr — see render-mode constants below

Render modes:

Constant Tr Effect
RenderFill 0 fill glyphs (default)
RenderStroke 1 stroke glyph outlines
RenderFillStroke 2 fill then stroke
RenderInvisible 3 neither — useful for an OCR text layer over an image
RenderFillClip 4 fill and add to clip
RenderStrokeClip 5 stroke and add to clip
RenderFSClip 6 fill, stroke and add to clip
RenderClip 7 add to clip only

Measuring and wrapping

w := p.TextWidth("Hello, pdfkit")             // width in points at the current font/size
lines := p.WrapText(longString, pdfkit.Mm(170)) // greedy word-wrap to a max width

WrapText breaks on spaces; a single word wider than maxWidth occupies its own line. Both return zero values when no font is selected.

How fonts are embedded

Every font a page uses is written as a Type0 composite font with Identity-H encoding:

  • An Identity CIDToGIDMap (character code == glyph index).
  • A per-glyph /W width array, so PDF viewers get correct advance widths without consulting the embedded program.
  • A /ToUnicode CMap built from the runes actually drawn, so copy/paste recovers the original text.
  • Only the glyphs actually drawn are embedded — the subset is computed from the document's recorded glyph usage at Write time, after all drawing has happened.

The outline format depends on the source font:

  • TrueType (glyf) outlines embed as a subsetted FontFile2 / CIDFontType2 with a /CIDToGIDMap stream, via Font.SubsetTrueType (composite-glyph components are followed so a subset never drops a referenced sub-glyph; the subset renumbers glyphs, and the map sends each CID — the original glyph id — to its subset id).
  • CFF/OpenType outlines embed as a charstring-subsetted FontFile3 / CIDFontType0, via Font.SubsetCFF, whose glyph numbering is preserved (so an Identity /CIDToGIDMap suffices) — except a CID-keyed CFF or a CFF2 (variable) font, which SubsetCFF cannot charstring-subset and which gracefully falls back to embedding the whole CFF/CFF2 table (see Scope and limitations).

Shaped text for complex scripts

p.TextShaped(x, y, "بيت", "liga")

TextShaped runs the go-opentype shaper's GSUB substitution and GPOS positioning tables against the string, then places each resulting glyph individually with its own Tm matrix, honouring per-glyph x/y offsets and advances. Use it for Arabic, Indic, CJK, or any Latin text needing ligatures, mark attachment or kerning that the default path doesn't apply. features names OpenType feature tags to enable (e.g. "liga").

The plain Text path stays a simple left-to-right codepoint→glyph (cmap) mapping with no shaping — reach for TextShaped whenever script correctness matters, and keep Text for simple Latin runs where the extra shaping pass isn't needed.

Font embedding architecture

go-opentype/opentype supplies every primitive PDF embedding needs: the descriptor scalars (units-per-em, bounding box, ascent/descent, cap height, italic angle, flags, StemV), the by-glyph advances for the /W array, and the glyph subsetters (Font.SubsetTrueType, Font.SubsetCFF) themselves. pdfkit keeps no private sfnt re-parse or subsetter of its own — it calls straight into go-opentype for parsing, metrics and subsetting alike.

Scope and limitations

  • Both outline flavours are subsetted: TrueType glyf fonts via go-opentype's SubsetTrueType and CFF/OpenType fonts via SubsetCFF (charstring subsetting, glyph numbering preserved). A CID-keyed CFF or a CFF2 (variable) font cannot be charstring-subsetted by the preserve-numbering path, so it falls back to embedding the whole CFF/CFF2 table.
  • Encryption, tagged PDF / PDF-A, interactive forms and annotations are not yet implemented.

Next: Images for placing JPEG/PNG artwork, or the API reference for the complete signature list.