PaperForge is a pure Elixir library for generating PDF documents directly, without browsers, operating-system binaries, or external rendering services.
It builds the PDF object graph, content streams, cross-reference table, and trailer entirely in Elixir.
PaperForge is currently in early development. The
0.1.xAPI may change while the document and graphics engines evolve.
Why PaperForge?
Many PDF-generation tools work by converting HTML through Chromium, wkhtmltopdf, or another external application.
PaperForge takes a different approach:
Elixir API
↓
Page and graphics operations
↓
PDF object graph
↓
Serializer
↓
Writer
↓
PDF binaryThis provides direct control over the generated PDF structure and creates a foundation for future document layout, HTML/CSS rendering, parsing, and editing capabilities.
Current features
PaperForge currently supports:
- Pure Elixir PDF generation
- Multiple pages
- A3, A4, A5, Letter, and Legal page sizes
- Custom page dimensions
- Portrait and landscape orientations
- Built-in Helvetica text
- Text size, position, and color
- Lines
- Rectangles
- Circles
- RGB and grayscale colors
- Basic document metadata
- PDF binary generation
- Direct file output
- Traditional PDF cross-reference tables
Installation
PaperForge is currently under active development and may not yet be published on Hex.
Add it directly from GitHub:
def deps do
[
{:paper_forge,
github: "Manuel1471/paper_forge",
branch: "main"}
]
endThen install dependencies:
mix deps.get
After the first stable Hex release, installation will use:
def deps do
[
{:paper_forge, "~> 0.1.0"}
]
endQuick start
alias PaperForge.Color
alias PaperForge.Page
document =
PaperForge.new()
|> PaperForge.metadata(
title: "PaperForge Example",
author: "Manuel Garcia",
subject: "Pure Elixir PDF generation",
keywords: ["Elixir", "PDF"]
)
|> PaperForge.add_page(fn page ->
page
|> Page.text(
"Hello from PaperForge",
x: 72,
y: 760,
size: 28,
color: Color.rgb255(35, 60, 120)
)
|> Page.line(
x1: 72,
y1: 740,
x2: 520,
y2: 740,
width: 2,
color: Color.rgb255(35, 60, 120)
)
end)
PaperForge.write!(document, "example.pdf")Drawing shapes
Lines
Page.line(
page,
x1: 72,
y1: 700,
x2: 300,
y2: 700,
width: 2,
color: Color.rgb255(40, 70, 140)
)Rectangles
Page.rectangle(
page,
x: 72,
y: 560,
width: 220,
height: 100,
fill: true,
stroke: true,
fill_color: Color.rgb255(235, 240, 250),
stroke_color: Color.rgb255(40, 70, 140),
line_width: 2
)Circles
Page.circle(
page,
x: 400,
y: 610,
radius: 50,
fill: true,
stroke: true,
fill_color: Color.rgb255(245, 180, 70),
stroke_color: Color.rgb255(120, 70, 20),
line_width: 2
)PaperForge approximates circles using four cubic Bézier curves because PDF does not provide a native circle operator.
Multiple pages
alias PaperForge.Page
document =
PaperForge.new()
|> PaperForge.add_page(fn page ->
Page.text(
page,
"First page",
x: 72,
y: 750,
size: 28
)
end)
|> PaperForge.add_page(
[size: :letter, orientation: :landscape],
fn page ->
Page.text(
page,
"Second page",
x: 72,
y: 500,
size: 28
)
end
)
PaperForge.write!(document, "multiple_pages.pdf")Page sizes
Supported page names:
:a3
:a4
:a5
:letter
:legalExample:
PaperForge.add_page(
document,
[size: :letter, orientation: :landscape],
fn page ->
Page.text(page, "Landscape page", x: 72, y: 500)
end
)Custom page dimensions are also supported:
Page.new(size: {500, 500})All dimensions are expressed in PDF points.
1 point = 1/72 inchCoordinate system
PaperForge currently uses the native PDF coordinate system.
Y
↑
│
│
│
└────────────→ X
(0, 0)The origin is located at the bottom-left corner of the page.
For an A4 page:
width: 595.28 points
height: 841.89 pointsExample:
Page.text(
page,
"Near the top",
x: 72,
y: 760
)A top-left coordinate abstraction is planned for a future release.
Colors
RGB using values from 0 to 1
Color.rgb(1, 0, 0)RGB using values from 0 to 255
Color.rgb255(255, 0, 0)Grayscale
Color.gray(0.5)
Color.black()
Color.white()Metadata
document =
PaperForge.new()
|> PaperForge.metadata(
title: "Monthly Report",
author: "Manuel Garcia",
subject: "Project summary",
keywords: ["report", "elixir", "pdf"],
creator: "PaperForge",
producer: "PaperForge"
)Metadata is written into the PDF Info dictionary and referenced from the document trailer.
Binary output
PaperForge can return the complete PDF as a binary:
pdf_binary =
document
|> PaperForge.to_binary()This can be used in Phoenix or Plug responses:
conn
|> put_resp_content_type("application/pdf")
|> put_resp_header(
"content-disposition",
~s(attachment; filename="document.pdf")
)
|> send_resp(200, PaperForge.to_binary(document))Architecture
PaperForge separates the public drawing API from the low-level PDF representation.
PaperForge
│
├── Page
│ └── High-level drawing operations
│
├── Graphics
│ ├── Text
│ ├── Line
│ ├── Rectangle
│ └── Circle
│
├── Document
│ └── PDF object graph and object allocation
│
├── Object
│ └── Indirect PDF objects
│
├── Reference
│ └── References such as `3 0 R`
│
├── Stream
│ └── Stream dictionaries and binary data
│
├── Serializer
│ └── Elixir values to PDF syntax
│
└── Writer
├── PDF header
├── Indirect objects
├── Cross-reference table
├── Trailer
└── EOF markerA page operation follows this pipeline:
Page.text/3
↓
PDF text operators
↓
Page content stream
↓
Indirect PDF object
↓
Serializer
↓
Writer
↓
PDF bytesPDF structure
PaperForge generates PDFs using a graph of indirect objects.
Trailer
└── Catalog
└── Page tree
├── Page
│ ├── Resources
│ └── Content stream
└── Page
├── Resources
└── Content streamObjects are connected through references such as:
3 0 RThis means:
object number: 3
generation: 0
type: indirect referenceDevelopment
Clone the repository:
git clone git@github.com:Manuel1471/paper_forge.git
cd paper_forge
Install local development tools:
mix local.hex --force
mix local.rebar --force
Run the test suite:
mix test
Compile with warnings treated as errors:
mix compile --warnings-as-errors
Format the source code:
mix format
Run all checks:
mix do format, compile --warnings-as-errors, test
Generate the graphics example:
mix run examples/graphics.exs
open tmp/paper_forge_0_1.pdf
Roadmap
0.2.0
- Text measurement
- Horizontal text alignment
- More built-in PDF fonts
- Stream compression
- JPEG image support
- Top-left coordinate helpers
- Improved metadata encoding
0.3.0
- Paragraph layout
- Automatic line wrapping
- Margins
- Vertical content flow
- Automatic page breaks
- Basic tables
Future
- TrueType and OpenType fonts
- Unicode text
- PNG images
- Reusable Form XObjects
- Links and annotations
- HTML parsing
- CSS style resolution
- HTML/CSS layout engine
- Existing PDF parsing
- Incremental updates
- Digital signatures
Project status
PaperForge is experimental and currently intended for learning, testing, and early integration.
The public API may change before version 1.0.0.
Contributing
Contributions, bug reports, architecture discussions, and PDF examples are welcome.
Before opening a pull request:
mix format
mix compile --warnings-as-errors
mix test
License
PaperForge is available under the terms specified in the LICENSE.