Codync
A free, open-source 1:1 alternative to Grok Bot and Muse. Message Claude Code, Codex, Cursor and 40+ coding agents as persistent bots from your iPhone, Mac or Linux desktop. Group chats, threads, appr
Pure-Go, zero-C PDF 1.7 writer: font subsetting/embedding (TrueType+CFF), graphics, images, shaped text. 100% test coverage.
Translation pending
Developer toolsGitHub @go-pdfkitOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Repository information has not been collected yet.
No updates detected yet. GitHub releases will appear here.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗A pure-Go, zero-C PDF 1.7 writer with a Go-idiomatic API. It embeds TrueType and OpenType/CFF fonts as subsetted composite fonts, draws vector graphics and text, and places JPEG and raster images. Fonts are parsed and shaped with go-opentype; nothing outside the Go standard library and our own pure-Go libraries is required.
pdfkit is the Go-native counterpart to our Ruby Prawn port
(go-ruby-prawn); here the API is
Go-idiomatic rather than a gem port.
io.Writer; optional Flate stream compression.q/Q state, and
constant-alpha transparency via ExtGState./W width array and a /ToUnicode CMap for
copy/paste. TrueType glyf → FontFile2 / CIDFontType2 (compact subset with
a /CIDToGIDMap stream); CFF/OpenType → FontFile3 / CIDFontType0 with
CFF charstring subsetting (only the used glyphs' charstrings are embedded).
Char/word spacing, leading, render modes, a simple wrapping helper, and an
optional shaped-text API (GSUB/GPOS via go-opentype) for Arabic/Indic/CJK.
A layout engine that places its own glyphs draws them with
Page.ShowGlyphs — glyph ids at the positions it computed, each with the
text it stands for — and hands over the fonts it already parsed with
NewFont(*opentype.Font).Page.Shade).image.Image
rasterised as XObjects (FlateDecode) with an /SMask for alpha, the colour
samples written straight (not premultiplied), as a soft mask without
/Matte requires.
Pixel-identical bitmaps are shared: each image is content-addressed by its
uncompressed samples, so a repeat is embedded — and compressed — once per
document and every placement, on any page, references the one XObject.Document.NewForm(bbox) returns a
Form, drawn with every Page method (paths, colours, alpha, shadings,
images, text, other forms) in its own form space. Page.DrawForm(f, rect)
maps its box onto a rectangle, and Page.PaintForm(f, a, b, c, d, e, f)
places it under any matrix, and Page.Document() reaches the document from
a page to make forms. A form painted any number of times, on any page,
is one stream: a logo, an emoji or a formula used thirty times costs thirty
Do operators. Forms may paint forms made before or after them. A cycle is
refused (ErrFormCycle). A form's content belongs to the tagged sequence it
is painted in (§14.7.4.2), so BeginTag and tagged links inside a form write
nothing.Pt/Mm/In unit helpers.pdfkit/widget: widget.Add and
widget.AddVector "print" a
go-widgets/toolkit widget tree onto a
page.
Add rasterises the tree and places it as an image XObject,
pixel-identical to the screen.AddVector instead emits PDF vector operators, so fills and strokes stay
crisp and text stays selectable. A TrueType-font widget label is embedded
in its own face as real Type0 text.pdfkit alone does not link the toolkit: 196 packages instead of
403, and a test keeps it that way.Options, output has no timestamps and a
content-derived /ID, so identical inputs produce byte-identical PDFs.go get github.com/go-pdfkit/pdfkit
package main
import (
"os"
"github.com/go-pdfkit/pdfkit"
)
func main() {
ttf, _ := os.ReadFile("font.ttf")
font, _ := pdfkit.LoadFont(ttf)
doc := pdfkit.New(pdfkit.Options{Title: "Hello"})
p := doc.AddPage(pdfkit.A4)
p.SetFont(font, 24)
p.Text(pdfkit.Mm(20), p.Height()-pdfkit.Mm(20), "Hello, pdfkit")
p.SetStrokeColor(pdfkit.RGB8(0x0d, 0x94, 0x88))
p.SetLineWidth(2)
p.MoveTo(pdfkit.Mm(20), pdfkit.Mm(20))
p.LineTo(pdfkit.Mm(190), pdfkit.Mm(20))
p.Stroke()
f, _ := os.Create("out.pdf")
defer f.Close()
doc.Write(f)
}
Shaped (complex-script) text uses p.TextShaped(x, y, s, features...), which
runs the go-opentype shaper so Arabic, Indic and CJK position correctly.
The run is written as one TJ array per baseline segment — a numeric
correction only where shaping departs from the font's own advances (kerning,
marks), none for plain text — and unchanged font/colour state is not
rewritten, so a page of prose is a few tens of KB compressed, not hundreds. The
default Text path stays a simple left-to-right cmap mapping.
Text a layout engine has already shaped and placed goes through
p.ShowGlyphs(x, y, glyphs): each pdfkit.Glyph is a glyph id, its origin's
offset from the run origin and the text it stands for (the /ToUnicode
entry). The same TJ encoding applies — a correction only where a position
departs from the font's own advance by a thousandth of an em or more, computed
from absolute positions so that the pen never drifts. go-webengine's
pdfcanvas draws a laid-out page this way.
import (
"github.com/go-pdfkit/pdfkit/widget"
"github.com/go-widgets/toolkit"
)
root := toolkit.NewContainer(toolkit.NewBoxLayout())
btn := toolkit.NewButton("Submit", nil)
btn.Style = toolkit.ButtonProminent
root.AddWidget(btn)
root.AddWidget(toolkit.NewLabel("Status: ready"))
rect := pdfkit.Rect{X: pdfkit.Mm(20), Y: pdfkit.Mm(200), Width: pdfkit.Mm(80), Height: pdfkit.Mm(30)}
// Raster: pixel-identical to the screen, not selectable.
_ = widget.Add(p, root, rect, nil)
// Vector: crisp fills/strokes, selectable text (needs an embedded Font).
rect.Y -= pdfkit.Mm(40)
_ = widget.AddVector(p, root, rect, &widget.Options{Font: font})
Options.Scale sets the layout-pixels-per-point ratio (default
widget.DefaultScale, 2). Options.Theme selects the toolkit theme (default
toolkit.DefaultLight()).
The widget bridge moved out of the root package. Make these replacements:
v0.19 (pdfkit) | v0.20 (pdfkit/widget) |
|---|---|
p.AddWidget(root, rect, opts) | widget.Add(p, root, rect, opts) |
p.AddWidgetVector(root, rect, opts) | widget.AddVector(p, root, rect, opts) |
pdfkit.WidgetOptions | widget.Options |
pdfkit.DefaultWidgetScale | widget.DefaultScale |
Nothing else changed. A program that never used the bridge only gets smaller.
GOWORK=off CGO_ENABLED=0 go test ./... runs the suite at exact 100%
statement coverage. Correctness is checked against an independent parser:
generated documents are re-opened with rsc.io/pdf
and their structure verified. The embedded TrueType subset is re-parsed with
go-opentype and each drawn glyph, resolved through the /CIDToGIDMap, is
confirmed contour-identical to the original; the embedded CFF subset is asserted
smaller than the whole CFF table and re-parsed so each kept glyph still renders
intact. Tests are deterministic and network-free: they use a synthesised
TrueType font, a synthesised CFF2 font and a bundled OFL OpenType/CFF font.
glyf fonts via
go-opentype's SubsetTrueType (compact renumbering + a /CIDToGIDMap
stream) and CFF/OpenType fonts via SubsetCFF (charstring subsetting,
glyph numbering preserved). All subsetting and the font-descriptor metrics come
straight from go-opentype; pdfkit keeps no private sfnt re-parse.CFF/CFF2 table.AddLink, AddNamedDest and
AddNamedLink put a clickable /Link with a URI or a named destination over
a rectangle. Text (sticky note) annotations too, since v0.16.0:
Page.AddTextAnnotation puts a note with its contents, author, subject,
icon, colour and opacity on a page — go-slides carries a slide's speaker
notes in them. No other annotation type is written, and nothing here draws
an appearance stream — go-pdfkit/render does that when reading.AddOutlineItem) nest by level; since v0.16.0
AddOutlineItemWith styles a title bold or italic and points it at a place on
the page ([page /XYZ x y null]) instead of fitting the whole page.Page.BeginActualText(s) …
EndMarkedContent() wrap what is drawn in a /Span with /ActualText, the
text extraction, copying and search use instead of the glyphs. An empty
string hides decorations from the text (numbers in a margin, a numbered disc
after a line of code): Poppler's pdftotext then leaves them out. Spans left
open are closed when the page is written.go-pdfkit/reader reads RC4-40 through AES-256 and writes AES-128 and
AES-256 with permissions and both passwords.go-pdfkit/forms reads, fills and flattens
an AcroForm, and go-pdfkit/xfa reads the other kind.Document.Structure() returns
the root of the logical structure (ISO 32000-1, 14.7), and Add builds the
tree in reading order with the standard types (, ,
, ///, ,
and with , , …).
… ties what is drawn to an element
(a marked-content sequence with an MCID, mapped back through the parent
tree). marks backgrounds, page numbers and ornaments,
and puts a link annotation under its Link element with
its . Notes get an element of their own, and elements
with no content are left out. After v0.18.0, carries
the standard structure attributes (§14.8.5): a 's , which
PDF/UA asks for, a cell's … , and
(the XMP metadata with and ) complete what PDF/UA-1
asks of the file; a small tagged document passes veraPDF's PDF/UA-1
profile. Tagging every drawing is the caller's part. An untagged document
is written as before.BSD-3-Clause — see LICENSE. Copyright (c) 2026 the go-pdfkit/pdfkit authors.
StructSectStructH1StructPStructLStructLIStructLblStructLBodyStructTableStructFigureStructFormulaAltStructCodeStructLinkPage.BeginTag(e)EndMarkedContent()Page.BeginArtifactPage.AddTaggedLink/ContentsAnnotStructElem.AttributesTHScopeRowSpanOptions.LangDisplayDocTitlePDFUAdc:titlepdfuaid:partgo-pdfkit/ops preserves a structure tree it is given around
the pages that survive.