GxPDF Architecture
March 23, 2026 · View on GitHub
Technical architecture overview of the GxPDF PDF library.
Version: v0.7.0+ Last Updated: 2026-03-23
Project Structure
github.com/coregx/gxpdf
├── gxpdf.go # Main public API entry point
├── cmd/gxpdf/ # CLI application
│ └── commands/ # CLI command implementations
├── builder/ # Declarative Builder API (high-level, user-facing)
│ └── internal/ # Builder internals (font bridge, PDF renderer)
├── layout/ # Pure computation layout engine (zero PDF dependencies)
├── signature/ # Digital signatures (PAdES B-B/B-T, CMS/PKCS#7, verify)
├── creator/ # PDF creation API (low-level primitives)
│ └── forms/ # Interactive form fields (AcroForm)
├── export/ # Export formats (CSV, JSON, Excel)
└── internal/ # Private implementation
├── document/ # Document model, pages, metadata
├── encoding/ # Stream codecs (Flate, DCT, ASCII85, LZW)
├── extractor/ # Text, image, graphics extraction
├── fonts/ # Font handling (Standard 14 + TTF/OTF)
├── models/ # Data models
│ ├── content/ # Content stream models
│ ├── table/ # Table/Cell rich models
│ └── types/ # Common types (Image, Rectangle)
├── parser/ # PDF file parsing
├── reader/ # PDF document reader
├── security/ # Encryption (RC4, AES-128/256)
├── tabledetect/ # Table detection (4-Pass Hybrid algorithm)
└── writer/ # PDF file generation
Generation Stack: Three-Layer Architecture
GxPDF provides two complementary generation APIs that share the same underlying rendering engine:
Layer 3: builder/ — User-facing declarative API
Layer 2: layout/ — Pure computation layout engine
Layer 1: creator/ — Low-level PDF primitives
Each layer has a clear responsibility and strict dependency direction — upper layers depend on lower layers, never the reverse.
Layer 3: builder/ — Declarative API
The builder package is the recommended entry point for document generation. It provides a QuestPDF-inspired API where documents are expressed as nested closures. The engine handles page breaks, header/footer repetition, and two-pass page number resolution automatically.
doc := builder.NewBuilder(
builder.WithPageSize(builder.A4),
builder.WithMargins(builder.Mm(20), builder.Mm(15), builder.Mm(20), builder.Mm(15)),
builder.WithTitle("Annual Report"),
)
doc.Page(func(page *builder.PageBuilder) {
page.Header(func(h *builder.Container) {
h.Text("ACME Corporation", builder.Bold(), builder.FontSize(14))
h.Line()
})
page.Content(func(c *builder.Container) {
c.Row(func(r *builder.RowBuilder) {
r.Col(8, func(col *builder.ColBuilder) {
col.Text("Main content area")
})
r.Col(4, func(col *builder.ColBuilder) {
col.Text("Sidebar", builder.TextColor(builder.Gray))
})
})
})
page.Footer(func(f *builder.Container) {
f.PageNumber(builder.PageNum+" of "+builder.TotalPages,
builder.AlignCenter(), builder.FontSize(8))
})
})
pdfBytes, err := doc.Build()
Key concepts:
Builder— document entry point, holds document-level configPageBuilder— defines Header/Content/Footer zones for a logical pageContainer— universal content receiver (text, rows, images, lines, spacers)RowBuilder/ColBuilder— 12-column grid with proportional sizingValue— unit-aware dimension type (Mm,Cm,In,Pt,Pct,Fr)Color— RGB color type with 13 predefined constants andHex()parser- All errors are accumulated and returned as a joined error from
Build()
Dependency rule: builder/ imports layout/ and creator/. It never exposes layout/ types in its public API — users work exclusively with builder.Value, builder.Color, builder.Size.
Layer 2: layout/ — Pure Computation Engine
The layout package is a zero-dependency layout engine. It takes a tree of Element values, computes their positions, and produces positioned Block values ready for rendering. It has no knowledge of PDF format, file I/O, or font loading.
Core types:
Element— interface withPlanLayout(Area) PlanPlan— result of layout: status (Full/Partial/Nothing), consumed height, list of BlocksBlock— a positioned rectangle with aDraw(Renderer)callbackRenderer— interface for drawing primitives (text, lines, images, rectangles)Paginator— walksPageDefelements, splits content across physical pagesFontResolver— interface for font metric queries (standard 14 + TTF)Style— typography (font family, size, weight, color, alignment, line height)
Pagination algorithm:
- For each
PageDef, render header and footer into fixed zones. - Feed content elements to
PlanLayoutwith the available body area. - When an element returns
Nothing(does not fit), flush the current page and continue on a new one. KeepTogetherboxes are pushed whole to the next page if they do not fit.- After all pages are resolved, a second pass replaces
PageNum/TotalPagesplaceholders with actual values.
Layer 1: creator/ — PDF Primitives
The creator package writes PDF objects, content streams, and cross-reference tables. It is the rendering backend for both the Builder API and direct low-level use.
c := creator.New()
c.SetTitle("Annual Report")
page, _ := c.NewPage()
page.AddText("Hello, World!", 100, 700, creator.Helvetica, 12)
page.DrawRectangle(100, 600, 200, 50, &creator.RectangleOptions{
FillColor: &creator.Blue,
})
c.WriteToFile("output.pdf")
Features:
- Text rendering with Standard 14 fonts
- Graphics (lines, rectangles, circles, polygons, arcs, Bezier curves)
- Linear and radial gradients (PDF Shading Type 2/3 with multi-stop support)
- JPEG/PNG image embedding
- Tables with merged cells
- Chapters with auto-generated TOC
- Headers and footers
- Annotations (sticky notes, highlights, stamps)
- Interactive forms (text fields, checkboxes, dropdowns)
- RC4/AES encryption
- Watermarks
Component Overview
Public API Layer
Main Entry Point (gxpdf.go)
// Open and extract from existing PDFs
doc, _ := gxpdf.Open("document.pdf")
tables := doc.ExtractTables()
text := doc.Page(0).Text()
Export API (export/)
exporter := export.NewCSVExporter()
exporter.Export(table, writer)
jsonExp := export.NewJSONExporter()
jsonExp.Export(table, writer)
excelExp := export.NewExcelExporter()
excelExp.Export(tables, "output.xlsx")
Internal Layer
Document Model (internal/document/)
Core document structure:
Document- PDF document with pages, metadataPage- Single page with dimensions, resourcesPageSize- Predefined sizes (A4, Letter, etc.)
Parser (internal/parser/)
PDF file parsing infrastructure:
- Lexer - Tokenizes PDF byte stream
- Object Parser - Parses PDF objects (arrays, dicts, streams)
- XRef Parser - Cross-reference table handling
- Stream Parser - Content stream parsing
Encoding (internal/encoding/)
Stream compression/decompression:
| Codec | Description | Status |
|---|---|---|
| FlateDecode | zlib compression (most common) | Implemented |
| DCTDecode | JPEG image data | Implemented |
| ASCII85Decode | ASCII encoding | Planned |
| ASCIIHexDecode | Hexadecimal encoding | Planned |
| LZWDecode | LZW compression (legacy) | Planned |
Note: FlateDecode and DCTDecode cover 95%+ of PDF files. Legacy codecs planned for v0.2.0.
Fonts (internal/fonts/)
Comprehensive font support:
Standard 14 Fonts (built-in, no embedding required):
- Helvetica family (Regular, Bold, Oblique, BoldOblique)
- Times family (Roman, Bold, Italic, BoldItalic)
- Courier family (Regular, Bold, Oblique, BoldOblique)
- Symbol, ZapfDingbats
TrueType/OpenType Fonts (embedded):
- TTF/OTF file parsing
- Font metrics extraction (Ascender, Descender, CapHeight, etc.)
- Glyph width calculation
- Font subsetting
- ToUnicode CMap generation for text extraction
Key Types:
// Standard 14 font (built-in PDF fonts, no embedding required)
type Standard14Font struct {
Name string // PostScript name (e.g., "Helvetica", "Times-Roman")
Family string // Font family (e.g., "Helvetica", "Times")
Weight string // Weight (e.g., "Regular", "Bold")
Style string // Style (e.g., "Normal", "Oblique", "Italic")
IsSymbolic bool // True for Symbol/ZapfDingbats fonts
}
// Parsed TrueType/OpenType font (all fields)
type TTFFont struct {
FilePath string // Path to font file
PostScriptName string // PostScript name from name table
Tables map[string]*TTFTable // All parsed tables
UnitsPerEm uint16 // Units per em (typically 1000 or 2048)
// Glyph data
CharToGlyph map[rune]uint16 // Unicode → glyph ID
GlyphWidths map[uint16]uint16 // Glyph ID → advance width
FontData []byte // Raw data for embedding
// From head table
FontBBox [4]int16 // Bounding box [xMin, yMin, xMax, yMax]
// From hhea table
Ascender int16 // Typographic ascender
Descender int16 // Typographic descender (negative)
LineGap int16 // Line gap
// From post table
ItalicAngle float64 // Italic angle in degrees
UnderlinePosition int16 // Underline position
UnderlineThickness int16 // Underline thickness
IsFixedPitch bool // True if monospaced
// From OS/2 table
CapHeight int16 // Height of capital letters
XHeight int16 // Height of lowercase x
WeightClass uint16 // Weight (100-900)
WidthClass uint16 // Width class (1-9)
FSType uint16 // Embedding licensing rights
TypoAscender int16 // OS/2 typographic ascender
TypoDescender int16 // OS/2 typographic descender
// Derived values
StemV int16 // Vertical stem width (estimated)
Flags uint32 // PDF font flags bitmap
}
// PDF FontDescriptor (metrics for embedded fonts)
type FontDescriptor struct {
FontName string // PostScript name
Flags uint32 // Font flags (FixedPitch, Serif, Italic, etc.)
FontBBox [4]int // Bounding box in PDF units (1000/em)
ItalicAngle float64 // Italic angle
Ascent int // Ascender in PDF units
Descent int // Descender in PDF units (negative)
CapHeight int // Cap height in PDF units
StemV int // Vertical stem width
XHeight int // X-height in PDF units
Leading int // Line spacing
FontFile2Ref int // Object reference to embedded font stream
}
Writer (internal/writer/)
PDF file generation:
- PdfWriter - Main PDF file writer
- ContentStreamWriter - PDF content stream generation
- ResourceDictionary - Font/image resource management
- TrueTypeFontWriter - Embedded font object generation
- Stream Compression - FlateDecode compression
TrueType Font Embedding generates:
- Font dictionary (
/Type /Font /Subtype /TrueType) - FontDescriptor (
/Type /FontDescriptor) - ToUnicode CMap stream (for text extraction)
- FontFile2 stream (compressed font data)
Security (internal/security/)
PDF encryption support:
| Algorithm | Key Length | Status |
|---|---|---|
| RC4 | 40-bit | Full |
| RC4 | 128-bit | Full |
| AES | 128-bit | Full |
| AES | 256-bit | Full |
Table Detection (internal/tabledetect/)
4-Pass Hybrid Algorithm for universal table extraction:
- Pass 1: Gap Detection - Adaptive vertical gap threshold
- Pass 2: Overlap Detection - Tabula-inspired line grouping
- Pass 3: Alignment Detection - Geometric column clustering
- Pass 4: Multi-line Cell Merger - Amount-based row discrimination
Key Innovation: Amount-based discrimination
// Works universally across all bank formats
isTransactionRow := hasAmount(row) // Has monetary amount = transaction
isContinuation := !hasAmount(row) // No amount = continuation text
Results: 100% accuracy on bank statements (740/740 transactions)
Design Principles
1. Simple Public API
Hide complexity behind intuitive interfaces:
// User sees simple API
doc, _ := gxpdf.Open("file.pdf")
tables := doc.ExtractTables()
// Complex 4-pass algorithm hidden inside
2. Internal Privacy
internal/ enforces API boundaries:
- External code cannot import
internal/ - Free to refactor without breaking users
- Clear separation of public and private
3. Rich Domain Model
Objects with behavior, not just data containers:
// Rich model example
type Page struct {
dimensions Rectangle
content ContentStream
resources *Resources
}
func (p *Page) AddText(text string, pos Position, font *Font) error {
// Validation and business logic in domain entity
if err := p.validatePosition(pos); err != nil {
return err
}
return p.content.AppendText(text, pos, font)
}
4. Functional Options
Configuration through options pattern:
c := creator.New()
c.SetEncryption(creator.EncryptionOptions{
UserPassword: "user123",
OwnerPassword: "owner456",
Algorithm: creator.EncryptionAES256,
Permissions: creator.PermissionPrint,
})
The builder API applies the same pattern at the document level:
doc := builder.NewBuilder(
builder.WithPageSize(builder.Letter),
builder.WithDefaultFontSize(11),
builder.WithAuthor("ACME Corp"),
)
5. Error Context
Errors with full context for debugging:
if err != nil {
return fmt.Errorf("parse xref at offset %d: %w", offset, err)
}
The builder accumulates errors and surfaces them all at once from Build(), so callers do not need to check errors at every content-adding step.
Testing Strategy
Test Types
# Unit tests
go test ./...
# Race detector
go test -race ./...
# Coverage
go test -cover ./...
# Benchmarks
go test -bench=. -benchmem ./...
Coverage Targets
| Component | Target | Current |
|---|---|---|
| Parser | 80% | Done |
| Fonts | 80% | Done |
| Writer | 75% | Done |
| Creator | 70% | Done |
| layout/ | 80% | 81% |
| builder/ | 80% | 81% |
Dependencies
Production: Standard library only (zero external dependencies)
Testing:
github.com/stretchr/testify- Assertions
Build:
- Go 1.25+
- golangci-lint
Performance Considerations
Memory Efficiency
- Streaming PDF parsing (not loading entire file)
- Font subsetting (embed only used glyphs)
- Stream compression (FlateDecode)
CPU Efficiency
- Table detection optimized for large documents
- Lazy evaluation where possible
- Efficient text encoding for embedded fonts
Future Roadmap
v0.7.0 "Builder & Signatures" (Done, unreleased)
- Declarative Builder API (layout/ + builder/)
- Enterprise Tables (ColSpan, RowSpan, header repeat, page split)
- Rich Text (mixed-style inline text with baseline alignment)
- Digital Signatures (PAdES B-B/B-T, CMS/PKCS#7, RSA + ECDSA, zero deps)
- Text Measurement API (Standard 14 + TTF font metrics)
- Coverage 80%+ on all new packages (layout 85.7%, builder 80.6%, signature 80.7%)
v0.8.0 "Generation Platform" (Planned)
- HTML to PDF (separate module, uses Builder API)
- QR Code + Barcode (Code128, EAN-13)
- PDF/A compliance (A-1b, A-2b)
- PDF/UA accessibility (tagged PDF)
- Ready components (invoice, report, letter templates)