A powerful, declarative layout engine for Go that enables you to create professional PDF documents with ease
Sahar is a modern layout engine designed for Go developers who need to generate complex PDF documents programmatically. Built with a component-based architecture, it provides an intuitive API for creating everything from simple reports to sophisticated document layouts.
- π― Declarative Syntax - Build layouts using composable, reusable components
- π Flexible Sizing - Support for fixed, fit-to-content, and grow sizing modes
- π¨ Rich Typography - Complete font control with size, family, color, and line height
- π¦ Smart Layout - Automatic positioning with precise alignment control
- π Visual Styling - Borders, backgrounds, padding, and spacing
- ποΈ Nested Structures - Create complex hierarchical document layouts
- π PDF Export - Direct rendering to PDF format
- π§ Type Safety - Fully typed Go API with compile-time safety
go get ella.to/saharHere's a simple example that creates a business card:
package main
import (
"os"
"ella.to/sahar"
)
func main() {
// Load fonts (required for text rendering)
err := sahar.LoadFonts("Arial", "./Arial.ttf")
if err != nil {
panic(err)
}
// Create a business card layout
card := sahar.Layout(
sahar.Box(
sahar.Sizing(sahar.Fixed(350), sahar.Fixed(200)),
sahar.Direction(sahar.TopToBottom),
sahar.Padding(20, 20, 20, 20),
sahar.BackgroundColor("#ffffff"),
sahar.Border(1),
sahar.BorderColor("#e0e0e0"),
// Header with logo and company info
sahar.Box(
sahar.Direction(sahar.LeftToRight),
sahar.Alignment(sahar.Left, sahar.Middle),
sahar.ChildGap(15),
sahar.Image("./logo.png",
sahar.Sizing(sahar.Fixed(40), sahar.Fixed(40)),
),
sahar.Box(
sahar.Direction(sahar.TopToBottom),
sahar.ChildGap(5),
sahar.Text("Tech Solutions Inc.",
sahar.FontType("Arial"),
sahar.FontSize(16),
sahar.FontColor("#2c3e50"),
),
sahar.Text("Innovation at Scale",
sahar.FontType("Arial"),
sahar.FontSize(10),
sahar.FontColor("#7f8c8d"),
),
),
),
// Contact information
sahar.Box(
sahar.Sizing(sahar.Grow(), sahar.Grow()),
sahar.Direction(sahar.TopToBottom),
sahar.Alignment(sahar.Right, sahar.Bottom),
sahar.ChildGap(3),
sahar.Text("John Doe",
sahar.FontType("Arial"),
sahar.FontSize(14),
sahar.FontColor("#2c3e50"),
),
sahar.Text("Senior Developer",
sahar.FontType("Arial"),
sahar.FontSize(11),
sahar.FontColor("#34495e"),
),
sahar.Text("john.doe@techsolutions.com",
sahar.FontType("Arial"),
sahar.FontSize(10),
sahar.FontColor("#7f8c8d"),
),
),
),
)
// Export to PDF
file, err := os.Create("business_card.pdf")
if err != nil {
panic(err)
}
defer file.Close()
err = sahar.RenderToPDF(file, card)
if err != nil {
panic(err)
}
}Sahar uses three fundamental node types to build layouts:
| Type | Purpose | Use Cases |
|---|---|---|
| Box | Container for other nodes | Sections, panels, layout containers |
| Text | Text content with typography | Headings, paragraphs, labels |
| Image | Image content | Logos, photos, charts, diagrams |
The flexible sizing system adapts to different layout needs:
| Type | Behavior | Best For |
|---|---|---|
| Fixed | Exact dimensions | Known sizes, consistent layouts |
| Fit | Size to content | Dynamic content, responsive design |
| Grow | Fill available space | Flexible sections, responsive layouts |
Control how child elements are arranged:
- LeftToRight - Horizontal arrangement (row)
- TopToBottom - Vertical arrangement (column)
Precise control over element positioning:
| Horizontal | Vertical | Description |
|---|---|---|
Left |
Top |
Align to start edges |
Center |
Middle |
Center alignment |
Right |
Bottom |
Align to end edges |
func CreateInvoice() *sahar.Node {
return sahar.Box(
sahar.Sizing(sahar.A4()...),
sahar.Direction(sahar.TopToBottom),
sahar.Padding(40, 40, 40, 40),
// Header
sahar.Box(
sahar.Direction(sahar.LeftToRight),
sahar.Alignment(sahar.Left, sahar.Top),
sahar.ChildGap(20),
sahar.Box(
sahar.Direction(sahar.TopToBottom),
sahar.Text("INVOICE",
sahar.FontSize(24),
sahar.FontColor("#2c3e50"),
),
sahar.Text("Invoice #INV-001",
sahar.FontSize(12),
sahar.FontColor("#7f8c8d"),
),
),
sahar.Box(
sahar.Sizing(sahar.Grow(), sahar.Fit()),
sahar.Alignment(sahar.Right, sahar.Top),
sahar.Text("Due: December 31, 2023",
sahar.FontSize(12),
sahar.FontColor("#e74c3c"),
),
),
),
// Items table would go here...
)
}func CreateReport() *sahar.Node {
return sahar.Box(
sahar.Sizing(sahar.USLetter()...),
sahar.Direction(sahar.TopToBottom),
sahar.ChildGap(30),
// Title
sahar.Text("Quarterly Report Q4 2023",
sahar.FontSize(20),
sahar.FontColor("#2c3e50"),
sahar.Alignment(sahar.Center, sahar.Top),
),
// Content grid
sahar.Box(
sahar.Direction(sahar.LeftToRight),
sahar.ChildGap(20),
// Left column - text content
sahar.Box(
sahar.Sizing(sahar.Fixed(300), sahar.Fit()),
sahar.Direction(sahar.TopToBottom),
sahar.ChildGap(15),
sahar.Text("Executive Summary",
sahar.FontSize(16),
sahar.FontColor("#34495e"),
),
sahar.Text("Revenue increased by 23% compared to Q3...",
sahar.FontSize(11),
sahar.FontColor("#2c3e50"),
),
),
// Right column - chart
sahar.Image("./chart.png",
sahar.Sizing(sahar.Grow(), sahar.Fixed(200)),
),
),
)
}func CreateForm() *sahar.Node {
return sahar.Box(
sahar.Direction(sahar.TopToBottom),
sahar.ChildGap(15),
sahar.Padding(30, 30, 30, 30),
formField("Full Name:", "John Doe"),
formField("Email:", "john@example.com"),
formField("Phone:", "+1 (555) 123-4567"),
)
}
func formField(label, value string) *sahar.Node {
return sahar.Box(
sahar.Direction(sahar.LeftToRight),
sahar.ChildGap(10),
sahar.Text(label,
sahar.Sizing(sahar.Fixed(100), sahar.Fit()),
sahar.FontSize(12),
sahar.FontColor("#2c3e50"),
),
sahar.Box(
sahar.Sizing(sahar.Grow(), sahar.Fit()),
sahar.Border(1),
sahar.BorderColor("#bdc3c7"),
sahar.Padding(8, 10, 8, 10),
sahar.Text(value,
sahar.FontSize(12),
sahar.FontColor("#34495e"),
),
),
)
}// Font styling options
sahar.Text("Styled Text",
sahar.FontSize(16), // Size in points
sahar.FontType("Arial"), // Font family
sahar.FontColor("#2c3e50"), // Hex color
)// Layout control
sahar.Box(
sahar.Padding(10, 15, 10, 15), // Top, Right, Bottom, Left
sahar.ChildGap(20), // Space between children
sahar.Direction(sahar.TopToBottom), // Layout direction
sahar.Alignment(sahar.Center, sahar.Middle),
)// Visual styling
sahar.Box(
sahar.BackgroundColor("#f8f9fa"), // Background color
sahar.Border(2), // Border width
sahar.BorderColor("#dee2e6"), // Border color
)// Flexible sizing with constraints
sahar.Box(
sahar.Sizing(
sahar.Fixed(300), // Fixed width
sahar.Fit( // Height fits content
sahar.Min(100), // Minimum height
sahar.Max(500), // Maximum height
),
),
)// Standard page sizes
sahar.Box(sahar.Sizing(sahar.A4()...)) // A4: 595.28 x 841.89 points
sahar.Box(sahar.Sizing(sahar.USLetter()...)) // US Letter: 612 x 792 points
sahar.Box(sahar.Sizing(sahar.USLegal()...)) // US Legal: 612 x 1008 points| Function | Signature | Description |
|---|---|---|
Box() |
Box(...nodeOpt) *Node |
Creates a container node |
Text() |
Text(string, ...textOpt) *Node |
Creates a text node |
Image() |
Image(string, ...nodeOpt) *Node |
Creates an image node |
Layout() |
Layout(*Node) *Node |
Processes layout calculations |
| Function | Parameters | Description |
|---|---|---|
Fixed() |
float64 |
Sets exact dimensions |
Fit() |
...fitOpt |
Size to fit content |
Grow() |
- | Expand to fill space |
Min() |
float64 |
Sets minimum constraint |
Max() |
float64 |
Sets maximum constraint |
| Function | Parameters | Description |
|---|---|---|
Direction() |
direction |
Sets layout direction |
Alignment() |
Horizontal, Vertical |
Sets alignment |
Padding() |
top, right, bottom, left |
Sets internal spacing |
ChildGap() |
float64 |
Sets spacing between children |
| Function | Parameters | Description |
|---|---|---|
FontSize() |
float64 |
Sets font size in points |
FontType() |
string |
Sets font family |
FontColor() |
string |
Sets text color (hex) |
| Function | Parameters | Description |
|---|---|---|
BackgroundColor() |
string |
Sets background color (hex) |
Border() |
float64 |
Sets border width |
BorderColor() |
string |
Sets border color (hex) |
| Function | Parameters | Description |
|---|---|---|
LoadFonts() |
...string |
Loads font files (name, path pairs) |
RenderToPDF() |
io.Writer, ...*Node |
Renders nodes to PDF |
func CreateMultiPageDocument() {
page1 := sahar.Layout(createCoverPage())
page2 := sahar.Layout(createContentPage())
page3 := sahar.Layout(createSummaryPage())
file, _ := os.Create("document.pdf")
defer file.Close()
sahar.RenderToPDF(file, page1, page2, page3)
}func CreateDynamicList(items []string) *sahar.Node {
children := make([]*sahar.Node, len(items))
for i, item := range items {
children[i] = sahar.Text(item, sahar.FontSize(12))
}
return sahar.Box(
sahar.Direction(sahar.TopToBottom),
sahar.ChildGap(5),
sahar.Children(children...),
)
}func Header(title string) *sahar.Node {
return sahar.Box(
sahar.Direction(sahar.LeftToRight),
sahar.Padding(0, 0, 20, 0),
sahar.Text(title, sahar.FontSize(18)),
)
}
func Section(title, content string) *sahar.Node {
return sahar.Box(
sahar.Direction(sahar.TopToBottom),
sahar.ChildGap(10),
Header(title),
sahar.Text(content, sahar.FontSize(12)),
)
}This project is licensed under the MIT License - see the LICENSE file for details.