Packages

A pure Elixir PDF generation engine with multi-page documents, text layout, vector graphics, standard PDF fonts, embedded TrueType fonts, Unicode visible text, JPEG and PNG images, transparency, compression, page margins, coordinate systems, and metadata.

Current section

Files

Jump to
paper_forge README.md
Raw

README.md

# PaperForge
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.x` API 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:
```text
Elixir API
Page and graphics operations
PDF object graph
Serializer
Writer
PDF binary
```
This 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:
```elixir
def deps do
[
{:paper_forge,
github: "Manuel1471/paper_forge",
branch: "main"}
]
end
```
Then install dependencies:
```bash
mix deps.get
```
After the first stable Hex release, installation will use:
```elixir
def deps do
[
{:paper_forge, "~> 0.1.0"}
]
end
```
## Quick start
```elixir
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
```elixir
Page.line(
page,
x1: 72,
y1: 700,
x2: 300,
y2: 700,
width: 2,
color: Color.rgb255(40, 70, 140)
)
```
### Rectangles
```elixir
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
```elixir
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
```elixir
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:
```elixir
:a3
:a4
:a5
:letter
:legal
```
Example:
```elixir
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:
```elixir
Page.new(size: {500, 500})
```
All dimensions are expressed in PDF points.
```text
1 point = 1/72 inch
```
## Coordinate system
PaperForge currently uses the native PDF coordinate system.
```text
Y
└────────────→ X
(0, 0)
```
The origin is located at the bottom-left corner of the page.
For an A4 page:
```text
width: 595.28 points
height: 841.89 points
```
Example:
```elixir
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
```elixir
Color.rgb(1, 0, 0)
```
### RGB using values from 0 to 255
```elixir
Color.rgb255(255, 0, 0)
```
### Grayscale
```elixir
Color.gray(0.5)
Color.black()
Color.white()
```
## Metadata
```elixir
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:
```elixir
pdf_binary =
document
|> PaperForge.to_binary()
```
This can be used in Phoenix or Plug responses:
```elixir
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.
```text
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 marker
```
A page operation follows this pipeline:
```text
Page.text/3
PDF text operators
Page content stream
Indirect PDF object
Serializer
Writer
PDF bytes
```
## PDF structure
PaperForge generates PDFs using a graph of indirect objects.
```text
Trailer
└── Catalog
└── Page tree
├── Page
│ ├── Resources
│ └── Content stream
└── Page
├── Resources
└── Content stream
```
Objects are connected through references such as:
```pdf
3 0 R
```
This means:
```text
object number: 3
generation: 0
type: indirect reference
```
## Development
Clone the repository:
```bash
git clone git@github.com:Manuel1471/paper_forge.git
cd paper_forge
```
Install local development tools:
```bash
mix local.hex --force
mix local.rebar --force
```
Run the test suite:
```bash
mix test
```
Compile with warnings treated as errors:
```bash
mix compile --warnings-as-errors
```
Format the source code:
```bash
mix format
```
Run all checks:
```bash
mix do format, compile --warnings-as-errors, test
```
Generate the graphics example:
```bash
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:
```bash
mix format
mix compile --warnings-as-errors
mix test
```
## License
PaperForge is available under the terms specified in the [LICENSE](license.html).