# ironpress
[](https://crates.io/crates/ironpress)
[](https://docs.rs/ironpress)
[](https://github.com/gastongouron/ironpress/actions)
[](https://codecov.io/gh/gastongouron/ironpress)
Pure Rust HTML/CSS/Markdown to PDF converter. No browser, no system dependencies.
<p align="center">
<a href="https://codecov.io/gh/gastongouron/ironpress">
<img src="https://codecov.io/gh/gastongouron/ironpress/graphs/sunburst.svg?token=w36XIAwRxG" alt="Coverage grid">
</a>
</p>
Other Rust PDF crates shell out to headless Chrome or wkhtmltopdf. ironpress does it natively with a built-in layout engine. No C libraries, no binaries to install, just `cargo add ironpress`.
## Table of Contents
- [Quick Start](#quick-start)
- [API Reference](#api-reference)
- [Markdown to PDF](#markdown-to-pdf)
- [HTML Elements](#html-elements)
- [CSS Support](#css-support)
- [Images](#images)
- [Tables](#tables)
- [Fonts](#fonts)
- [Streaming Output](#streaming-output)
- [Async API](#async-api)
- [Security](#security)
- [How It Works](#how-it-works)
- [What's Next](#whats-next)
- [License](#license)
## Quick Start
```rust
use ironpress::html_to_pdf;
let pdf_bytes = html_to_pdf("<h1>Hello</h1><p>World</p>").unwrap();
std::fs::write("output.pdf", pdf_bytes).unwrap();
```
## API Reference
### One-liner functions
```rust
// HTML string to PDF bytes
let pdf = ironpress::html_to_pdf("<h1>Title</h1><p>Content</p>").unwrap();
// Markdown string to PDF bytes
let pdf = ironpress::markdown_to_pdf("# Title\n\nContent").unwrap();
// HTML file to PDF file
ironpress::convert_file("input.html", "output.pdf").unwrap();
// Markdown file to PDF file
ironpress::convert_markdown_file("input.md", "output.pdf").unwrap();
```
### Builder API
```rust
use ironpress::{HtmlConverter, PageSize, Margin};
let pdf = HtmlConverter::new()
.page_size(PageSize::LETTER) // default: A4
.margin(Margin::uniform(54.0)) // default: 72pt (1 inch)
.sanitize(false) // default: true
.convert("<h1>Custom page</h1>")
.unwrap();
```
### Custom fonts
```rust
use ironpress::HtmlConverter;
let ttf_data = std::fs::read("fonts/MyFont.ttf").unwrap();
let pdf = HtmlConverter::new()
.add_font("MyFont", ttf_data)
.convert(r#"<p style="font-family: MyFont">Custom font text</p>"#)
.unwrap();
```
### Page sizes
```rust
use ironpress::PageSize;
PageSize::A4 // 595.28 x 841.89 pt (default)
PageSize::LETTER // 612.0 x 792.0 pt
PageSize::LEGAL // 612.0 x 1008.0 pt
PageSize::new(width_pt, height_pt) // custom
```
### Margins
```rust
use ironpress::Margin;
Margin::default() // 72pt on all sides (1 inch)
Margin::uniform(54.0) // same value on all sides
Margin::new(top, right, bottom, left) // individual values in pt
```
## Markdown to PDF
Built-in Markdown parser with zero external dependencies.
```rust
let pdf = ironpress::markdown_to_pdf(r#"
# Project Title
Some **bold** and *italic* text with `inline code`.
## Features
- Item one
- Item two
- Item three
1. First
2. Second
> A wise quote
---
[Link text](https://example.com)
"#).unwrap();
```
Supported Markdown syntax: headings (`#` to `######`), bold (`**`), italic (`*`), bold+italic (`***`), inline code, fenced code blocks, links, images, unordered lists (`-`, `*`, `+`), ordered lists, blockquotes, and horizontal rules.
## HTML Elements
| Headings | `<h1>` through `<h6>` with default sizes and bold |
| Block containers | `<p>`, `<div>`, `<blockquote>`, `<pre>`, `<figure>`, `<figcaption>`, `<address>` |
| Semantic sections | `<section>`, `<article>`, `<nav>`, `<header>`, `<footer>`, `<main>`, `<aside>`, `<details>`, `<summary>` |
| Inline formatting | `<strong>`, `<b>`, `<em>`, `<i>`, `<u>`, `<small>`, `<sub>`, `<sup>`, `<code>`, `<abbr>`, `<span>` |
| Text decoration | `<del>`, `<s>` (strikethrough), `<ins>` (underline), `<mark>` (highlight) |
| Links | `<a>` with clickable PDF link annotations |
| Images | `<img>` with JPEG and PNG support (data URIs and local files) |
| Line breaks | `<br>`, `<hr>` |
| Lists | `<ul>`, `<ol>` with nested support, `<li>`, `<dl>`, `<dt>`, `<dd>` |
| Tables | `<table>`, `<thead>`, `<tbody>`, `<tfoot>`, `<tr>`, `<td>`, `<th>`, `<caption>` with colspan, rowspan, auto-sized columns, and cell borders |
## CSS Support
### Properties
| Typography | `font-size`, `font-weight`, `font-style`, `font-family`, `letter-spacing`, `word-spacing`, `text-indent`, `text-transform`, `white-space`, `vertical-align`, `text-overflow` |
| Colors | `color`, `background-color`, `opacity` |
| Box model | `margin` (including `auto`), `padding`, `border`, `border-width`, `border-color`, `border-radius`, `outline`, `outline-width`, `outline-color`, `box-sizing`, `width`, `height`, `min-width`, `min-height`, `max-width`, `max-height` |
| Layout | `text-align` (left, center, right, justify), `line-height`, `display` (none, block, inline, flex, grid), `float` (left, right), `clear`, `position` (static, relative, absolute), `z-index` |
| Flexbox | `flex-direction`, `justify-content`, `align-items`, `flex-wrap`, `gap` |
| Grid | `grid-template-columns` (fixed, `fr`, `auto`), `grid-gap` |
| Positioning | `top`, `left`, `z-index` |
| Visual effects | `box-shadow`, `transform` (rotate, scale, translate), `overflow` (visible, hidden), `visibility` |
| Backgrounds | `background-color`, `background-position`, `background-size`, `background-repeat`, `linear-gradient()`, `radial-gradient()` |
| Decoration | `text-decoration` (underline, line-through) |
| Lists | `list-style-type` (disc, circle, square, decimal, lower-alpha, upper-alpha, lower-roman, upper-roman, none), `list-style-position` (inside, outside) |
| Tables | `border-collapse`, `border-spacing` |
| Counters | `counter-reset`, `counter-increment`, `content: counter()` |
| Pseudo-elements | `::before`, `::after` with `content` property |
| Custom properties | `--my-var: value`, `var(--my-var)`, `var(--my-var, fallback)` |
| Functions | `calc()` (with `+`, `-`, `*`, `/` and mixed units) |
| Page control | `page-break-before`, `page-break-after`, `@page` (size, margin) |
All shorthand properties are supported. Margin and padding accept 1, 2, 3, or 4 values. Border accepts `width style color` shorthand.
### `<style>` blocks
```html
<style>
p { color: navy; font-size: 14pt }
.highlight { background-color: yellow; font-weight: bold }
#title { font-size: 24pt }
h1, h2 { color: darkblue }
@media print {
.screen-only { display: none }
}
</style>
```
### Selectors
| Tag | `p`, `h1`, `div` |
| Class | `.highlight`, `.intro` |
| ID | `#title`, `#nav` |
| Combined | `p.highlight`, `div#main` |
| Comma-separated | `h1, h2, h3` |
| Descendant | `div p`, `article h2` |
| Child | `div > p`, `ul > li` |
| Adjacent sibling | `h1 + p` |
| General sibling | `h1 ~ p` |
| Attribute | `[href]`, `[type="text"]` |
| Pseudo-class | `:first-child`, `:last-child`, `:nth-child()`, `:not()` |
| Pseudo-element | `::before`, `::after` |
### Values
| Colors | `red`, `navy`, `darkblue`, `#f00`, `#ff0000`, `rgb(255, 0, 0)` |
| Units | `12pt`, `16px`, `1.5em`, `50%`, `2rem`, `10vw`, `5vh` |
| Functions | `calc(100% - 20pt)`, `var(--my-color)`, `var(--size, 12pt)` |
| Keywords | `bold`, `italic`, `center`, `justify`, `none`, `inherit`, `initial`, `unset` |
### Media queries
`@media print` rules are applied (since PDF is print output). `@media screen` rules are ignored.
### `@page` rule
Control page size and margins from CSS:
```html
<style>
@page { size: letter landscape; margin: 0.5in; }
</style>
```
Supported values: `A4`, `letter`, `legal`, `landscape`, custom dimensions (`210mm 297mm`), and individual margins.
## Images
JPEG and PNG images are supported via data URIs and local file paths.
```html
<img src="data:image/jpeg;base64,/9j/4AAQ..." width="200" height="150">
<img src="photo.jpg" width="300" height="200">
```
Images are embedded directly in the PDF. JPEG uses DCTDecode, PNG uses FlateDecode with PNG predictors. Width and height attributes are converted from px to pt.
## Tables
Full table support with sections, spanning, auto-sized columns, and styling.
```html
<table>
<thead>
<tr><th>Name</th><th>Role</th><th>Status</th></tr>
</thead>
<tbody>
<tr>
<td rowspan="2">Alice</td>
<td>Engineer</td>
<td>Active</td>
</tr>
<tr>
<td colspan="2">On project X</td>
</tr>
<tr>
<td>Bob</td>
<td>Designer</td>
<td>Active</td>
</tr>
</tbody>
</table>
```
Column widths are automatically calculated based on content. Features: `<thead>`, `<tbody>`, `<tfoot>` sections, `colspan` and `rowspan` attributes, bold headers in `<th>`, cell borders, background colors, and padding.
## Fonts
### Standard fonts
ironpress includes the 14 standard PDF fonts (no embedding required). CSS `font-family` values are mapped to the closest match:
| Helvetica | `arial`, `helvetica`, `sans-serif`, `verdana`, `tahoma`, `roboto`, `open sans`, `inter`, `system-ui`, and 20+ more |
| Times-Roman | `serif`, `times new roman`, `georgia`, `garamond`, `palatino`, `merriweather`, `lora`, and 15+ more |
| Courier | `monospace`, `courier new`, `consolas`, `fira code`, `jetbrains mono`, `source code pro`, `menlo`, and 15+ more |
Each family includes regular, bold, italic, and bold-italic variants (12 fonts total).
### Custom fonts (TrueType)
Embed any TTF font for pixel-perfect rendering:
```rust
use ironpress::HtmlConverter;
let font = std::fs::read("fonts/Inter.ttf").unwrap();
let pdf = HtmlConverter::new()
.add_font("Inter", font)
.convert(r#"<p style="font-family: Inter">Rendered with Inter</p>"#)
.unwrap();
```
The TTF parser extracts character metrics for accurate text wrapping and embeds the font directly in the PDF.
### `@font-face`
Load fonts directly from CSS (local files only, remote URLs are blocked for security):
```html
<style>
@font-face {
font-family: "MyFont";
src: url("fonts/MyFont.ttf");
}
p { font-family: MyFont; }
</style>
<p>Rendered with MyFont</p>
```
Requires `.base_path()` on the builder so the converter knows where to find font files.
## Streaming Output
Write PDF output directly to any `std::io::Write` implementation instead of allocating a `Vec<u8>`:
```rust
use std::fs::File;
let mut file = File::create("output.pdf").unwrap();
ironpress::html_to_pdf_writer("<h1>Hello</h1>", &mut file).unwrap();
```
Also available on the builder:
```rust
use ironpress::HtmlConverter;
use std::fs::File;
let mut file = File::create("output.pdf").unwrap();
HtmlConverter::new()
.convert_to_writer("<h1>Hello</h1>", &mut file)
.unwrap();
```
## Async API
Enable the `async` feature for async file I/O:
```toml
ironpress = { version = "0.7", features = ["async"] }
```
```rust
ironpress::convert_file_async("input.html", "output.pdf").await.unwrap();
ironpress::convert_markdown_file_async("input.md", "output.pdf").await.unwrap();
```
The HTML parsing, layout, and rendering remain synchronous (CPU-bound). Async is used for file reads and writes via tokio.
## Security
HTML is sanitized by default before conversion:
- `<script>`, `<iframe>`, `<object>`, `<embed>`, `<form>` tags are stripped
- `<style>` tags are preserved but dangerous CSS (external `url()`, `expression()`) is removed
- `@import` and `@font-face` only load local files (remote URLs are blocked)
- Event handlers (`onclick`, `onload`, etc.) are removed
- `javascript:` URLs are neutralized
- Input size (10 MB) and nesting depth (100 levels) are limited
Sanitization can be disabled with `.sanitize(false)` if you trust the input.
## How It Works
```
Input --> Sanitize --> Parse (html5ever) --> Extract <style> --> Style cascade --> Layout engine --> PDF
```
1. **Sanitize** the input HTML to remove dangerous elements
2. **Parse** HTML into a DOM tree using html5ever, extracting `<style>` blocks
3. **Resolve styles** by cascading: tag defaults, then `@media print` rules, then stylesheet rules, then inline CSS
4. **Layout** elements with text wrapping, float positioning, page breaks, tables, lists, images, and the CSS box model
5. **Render** to PDF 1.4 with text, graphics, link annotations, embedded images, and custom fonts
For Markdown input, a built-in parser converts Markdown to HTML first (no external dependencies).
## What's Next
ironpress focuses on being the best HTML/CSS/Markdown to PDF engine in Rust. Other input formats (SVG, DOCX, EPUB, CSV) will be available as separate crates in the ironpress ecosystem.
Remaining work:
- [ ] Hyphenation and advanced text shaping
- [ ] WASM support for browser-side PDF generation
- [ ] Inline SVG rendering
## License
MIT