sahar

package module
v0.1.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 28, 2025 License: MIT Imports: 10 Imported by: 0

README

Sahar Layout Engine

Go Reference Go Report Card

A powerful, declarative layout engine for Go that enables you to create professional PDF documents with ease

🚀 Overview

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.

✨ Key Features
  • 🎯 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

📦 Installation

go get ella.to/sahar

🏃 Quick Start

Here'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("[email protected]",
                    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)
    }
}

🎯 Core Concepts

Node Types

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
Sizing System

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
Layout Directions

Control how child elements are arranged:

  • LeftToRight - Horizontal arrangement (row)
  • TopToBottom - Vertical arrangement (column)
Alignment Options

Precise control over element positioning:

Horizontal Vertical Description
Left Top Align to start edges
Center Middle Center alignment
Right Bottom Align to end edges

📚 Examples & Use Cases

1. Invoice Layout
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...
    )
}
2. Report with Charts
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)),
            ),
        ),
    )
}
3. Form Layout
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:", "[email protected]"),
        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"),
            ),
        ),
    )
}

🎨 Styling Guide

Typography
// Font styling options
sahar.Text("Styled Text",
    sahar.FontSize(16),              // Size in points
    sahar.FontType("Arial"),         // Font family
    sahar.FontColor("#2c3e50"),      // Hex color
)
Spacing & Layout
// 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 Design
// Visual styling
sahar.Box(
    sahar.BackgroundColor("#f8f9fa"),   // Background color
    sahar.Border(2),                    // Border width
    sahar.BorderColor("#dee2e6"),       // Border color
)
Advanced Sizing
// 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
        ),
    ),
)
Page Presets
// 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

📖 API Reference

Core Functions
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
Sizing Functions
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
Layout Options
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
Typography
Function Parameters Description
FontSize() float64 Sets font size in points
FontType() string Sets font family
FontColor() string Sets text color (hex)
Visual Styling
Function Parameters Description
BackgroundColor() string Sets background color (hex)
Border() float64 Sets border width
BorderColor() string Sets border color (hex)
PDF Generation
Function Parameters Description
LoadFonts() ...string Loads font files (name, path pairs)
RenderToPDF() io.Writer, ...*Node Renders nodes to PDF

🔧 Advanced Usage

Multi-Page Documents
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)
}
Dynamic Content
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...),
    )
}
Component Composition
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)),
    )
}

📝 License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Index

Constants

View Source
const (
	LeftToRight direction = iota
	TopToBottom
)

Variables

This section is empty.

Functions

func A4

func A4() []sizingOpt

A4 is a custom sizing and should be used with Sizing please use it as spread for example: Sizing(A4()...)

func Alignment added in v0.1.0

func Alignment(horizontal Horizontal, vertical Vertical) nodeOpt

Alignment sets the horizontal and vertical alignment of the node. Horizontal can be Left, Center, or Right. Vertical can be Top, Middle, or Bottom.

func BackgroundColor

func BackgroundColor(color string) nodeOpt

BackgroundColor set the background color and the value should be hex color either it it has to be either 6 or 7 (including # at the beginning of the color value)

func Border

func Border(width float64) border

Border creates a border option for nodes.

func BorderColor added in v0.1.0

func BorderColor(color string) nodeOpt

BorderColor set the border color and the value should be hex color either it it has to be either 6 or 7 (including # at the beginning of the color value)

func ChildGap added in v0.1.0

func ChildGap(gap float64) nodeOpt

ChildGap sets the gap between child nodes in a parent node.

func Children added in v0.1.3

func Children(nodes ...*Node) nodeOpt

Children appends new children to the parent node this is useful if you have a dynamic component

func Direction added in v0.1.0

func Direction(dir direction) nodeOpt

Direction use for rendering the direction of the children. There is two possibilities either TopToBottom ot LeftToRight

func Fit added in v0.1.0

func Fit(opts ...fitOpt) sizingOpt

Fit mostly used in Parent nodes to let the layout engine know that it can expand to fit the children, This is the default value of all nodes

func Fixed added in v0.1.0

func Fixed(value float64) sizingOpt

Fixed accept a value and make sure the element has the value

func FontColor added in v0.1.0

func FontColor(color string) textOpt

FontColor sets the font color for text nodes.

func FontSize

func FontSize(size float64) textOpt

FontSize sets the font size for text nodes.

func FontType added in v0.1.0

func FontType(fontType string) textOpt

FontType sets the font type for text nodes.

func Grow added in v0.1.0

func Grow() sizingOpt

Grow is a way to set either width or height to gorw and fill the remaining of the space

func LoadFonts added in v0.1.0

func LoadFonts(src ...string) error

func Max added in v0.1.0

func Max(value float64) fitOpt

Max is set the value for Width or Height if they are set to either Fit or Grow

func Min added in v0.1.0

func Min(value float64) fitOpt

Min is set the value for Width or Height if they are set to either Fit or Grow

func Padding

func Padding(top, right, bottom, left float64) nodeOpt

Padding sets the padding for the node.

func RenderToPDF added in v0.1.0

func RenderToPDF(writer io.Writer, nodes ...*Node) error

RenderToPDF renders the node tree to a PDF and writes it to the provided writer

func RenderToPDFWithOptions added in v0.1.0

func RenderToPDFWithOptions(root *Node, writer io.Writer, options PDFOptions) error

RenderToPDFWithOptions renders the node tree to PDF with additional options

func Sizing added in v0.1.0

func Sizing(opts ...sizingOpt) nodeOpt

Sizing accept either 0, 1 or 2. If you pass more than 2 arguments it panics 0: default value to use Fit type for both width and height. They will expand to fit the children size 1: Set the Width of the node and set the height to fit 2: Set both Width and Height of the node

func USLegal added in v0.1.3

func USLegal() []sizingOpt

USLegal is a custom sizing and should be used with Sizing please use it as spread for example: Sizing(USLegal()...)

func USLetter added in v0.1.3

func USLetter() []sizingOpt

USLetter is a custom sizing and should be used with Sizing please use it as spread for example: Sizing(USLetter()...)

Types

type Horizontal

type Horizontal int

Horizontal represents the horizontal alignment of a node. It can be Left, Center, or Right.

const (
	Left Horizontal = iota
	Center
	Right
)

type Node

type Node struct {
	Direction       direction
	Type            Type
	Value           string  // For Text nodes
	FontColor       string  // For Text nodes
	FontSize        float64 // For Text nodes
	FontType        string  // For Text nodes
	FontLineHeight  float64 // For Text nodes
	Position        Position
	ChildGap        float64 // Space between children
	Width, Height   Size
	Padding         [4]float64 // Top, Right, Bottom, Left
	Horizontal      Horizontal
	Vertical        Vertical
	Parent          *Node
	Children        []*Node
	Border          float64 // Border width for Box nodes
	BorderColor     string  // Border color for Box nodes
	BackgroundColor string  // Background color for Box nodes
}

Node represents a layout node. It can be a box, text, or image. It contains properties for alignment, size, padding, and children nodes.

func Box added in v0.1.0

func Box(opts ...nodeOpt) *Node

Box creates a new box node with the specified options. A box node is a container that can hold other nodes and can have a border, background

func Image

func Image(src string, opts ...nodeOpt) *Node

Image creates a new image node with the specified source and options.

func Layout added in v0.1.0

func Layout(root *Node) *Node

Layout performs multi-pass layout calculation on the node tree

func Text

func Text(value string, opts ...textOpt) *Node

Text creates a new text node with the specified value and options. A text node is used to display text with specific font, size, and color. It can also have a border for debugging purposes.

type PDFOptions added in v0.1.0

type PDFOptions struct {
	Landscape       bool
	PageSize        string // "A4", "A3", "Letter", etc.
	MarginTop       float64
	MarginRight     float64
	MarginBottom    float64
	MarginLeft      float64
	DefaultFont     string
	DefaultFontSize float64
}

PDFOptions contains options for PDF rendering

func DefaultPDFOptions added in v0.1.0

func DefaultPDFOptions() PDFOptions

DefaultPDFOptions returns default PDF rendering options

type Position added in v0.1.0

type Position struct {
	X, Y float64
}

Position represents the position of a node in the layout. It contains X and Y coordinates and it will be calculated by the layout engine.

type Size

type Size struct {
	Type  SizeType
	Value float64
	// if the value of min or max set to -1
	// it means that the value is not set yet
	Min float64
	Max float64
}

Size represents the size of a node. It contains the type of size (Fit, Fixed, or Grow), the value, and optional min and max values.

type SizeType added in v0.1.0

type SizeType int
const (
	// FitType is used when the size of the node should fit its content.
	FitType SizeType = iota
	// FixedType is used when the size of the node is fixed to a specific value.
	FixedType
	// GrowType is used when the size of the node should grow to fill available space.
	GrowType
)

type Type

type Type int

Type represents the type of a node. It can be BoxType, TextType, or ImageType.

const (
	BoxType Type = iota
	TextType
	ImageType
)

type Vertical

type Vertical int

Vertical represents the vertical alignment of a node. It can be Top, Middle, or Bottom.

const (
	Top Vertical = iota
	Middle
	Bottom
)

Directories

Path Synopsis
examples
basic command
invoice command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL