HTML to PDF Conversion
The core feature of printwell is high-fidelity HTML to PDF conversion using Chromium’s Blink rendering engine.
How It Works
printwell embeds Chromium’s rendering engine (Blink + Skia) as a native library. When you convert HTML to PDF:
- HTML is parsed and a DOM tree is built
- CSS is applied and the layout is computed
- The page is rendered using Skia graphics
- PDF is generated using PDFium
This gives you the exact same rendering quality as Chrome, without needing a browser installation.
Supported Features
HTML5
- Full HTML5 support
- Semantic elements (
<article>,<section>,<nav>, etc.) - Forms (for visual rendering; see Form Fields for interactive forms)
- Tables with complex layouts
- SVG graphics (inline and external)
- Canvas elements (static rendering)
CSS3
- Flexbox and Grid layouts
- CSS Variables (custom properties)
- Media queries (print media is used)
- CSS animations (final frame is captured)
- Web fonts (@font-face)
- CSS transforms and filters
- Multi-column layouts
- CSS Paged Media (
@pagerules)
Print-Specific CSS
@page {
size: A4 portrait;
margin: 2cm;
}
@page :first {
margin-top: 5cm;
}
.page-break {
page-break-after: always;
}
@media print {
.no-print { display: none; }
a::after { content: " (" attr(href) ")"; }
}
Page Configuration
Page Sizes
| Size | Dimensions (mm) |
|---|---|
| A3 | 297 × 420 |
| A4 | 210 × 297 |
| A5 | 148 × 210 |
| Letter | 216 × 279 |
| Legal | 216 × 356 |
| Tabloid | 279 × 432 |
Or specify custom dimensions:
#![allow(unused)]
fn main() {
let options = PdfOptions::builder()
.page_width_mm(200.0)
.page_height_mm(300.0)
.build();
}
Margins
Margins are specified in millimeters:
#![allow(unused)]
fn main() {
// Individual margins
let margins = Margins::new(20.0, 15.0, 20.0, 15.0); // top, right, bottom, left
// Uniform margins
let margins = Margins::uniform(10.0);
// Symmetric margins
let margins = Margins::symmetric(20.0, 15.0); // vertical, horizontal
// No margins
let margins = Margins::none();
}
Headers and Footers
Headers and footers use HTML templates with special CSS classes:
<div style="font-size: 10px; width: 100%; text-align: center;">
<span class="title"></span> |
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
</div>
Available classes:
title- Document titlepageNumber- Current page numbertotalPages- Total page counturl- Document URLdate- Current date
Resource Loading
Remote Resources
By default, remote resources (images, fonts, stylesheets) are loaded:
#![allow(unused)]
fn main() {
let options = RenderOptions::builder()
.resources(ResourceOptions {
allow_remote: true,
timeout_ms: 30000,
max_concurrent: 6,
..Default::default()
})
.build();
}
Blocking Domains
Block specific domains (e.g., ads, analytics):
#![allow(unused)]
fn main() {
let options = RenderOptions::builder()
.resources(ResourceOptions {
blocked_domains: vec![
"ads.example.com".into(),
"analytics.example.com".into(),
],
..Default::default()
})
.build();
}
Base URL
Set a base URL for relative resources:
#![allow(unused)]
fn main() {
let options = RenderOptions::builder()
.base_url("https://example.com/assets/")
.build();
}
Web Fonts
Web fonts are fully supported:
<style>
@font-face {
font-family: 'CustomFont';
src: url('https://example.com/fonts/custom.woff2') format('woff2');
}
body {
font-family: 'CustomFont', sans-serif;
}
</style>
Configure font defaults:
#![allow(unused)]
fn main() {
let options = RenderOptions::builder()
.fonts(FontOptions {
default_sans_serif: "Arial".into(),
default_serif: "Times New Roman".into(),
default_monospace: "Courier New".into(),
use_system_fonts: true,
enable_web_fonts: true,
..Default::default()
})
.build();
}
Viewport Configuration
Control the viewport for rendering:
#![allow(unused)]
fn main() {
let options = RenderOptions::builder()
.viewport(Viewport {
width: 1920,
height: 1080,
device_scale_factor: 2.0, // Retina/HiDPI
})
.build();
}
Scale Factor
Adjust the overall scale:
#![allow(unused)]
fn main() {
let options = PdfOptions::builder()
.scale(0.8) // 80% scale
.build();
}
Valid range: 0.1 to 2.0
Background Graphics
Control background printing:
#![allow(unused)]
fn main() {
// Include backgrounds (default)
let options = PdfOptions::builder()
.print_background(true)
.build();
// Exclude backgrounds
let options = PdfOptions::builder()
.print_background(false)
.build();
}
Page Ranges
Select specific pages:
#![allow(unused)]
fn main() {
let options = PdfOptions::builder()
.page_ranges("1-5,8,10-12")
.build();
}
Metadata
Set PDF metadata:
#![allow(unused)]
fn main() {
let options = PdfOptions::builder()
.metadata(PdfMetadata {
title: Some("My Document".into()),
author: Some("Author Name".into()),
subject: Some("Document Subject".into()),
keywords: Some("pdf, rust, printwell".into()),
creator: Some("My Application".into()),
producer: Some("printwell".into()),
})
.build();
}
Performance Tips
- Reuse Converter - Create one
Converterinstance and reuse it - Use ConverterPool - For batch processing, use a pool
- Minimize remote resources - Embed resources when possible
- Set appropriate timeouts - Avoid waiting for slow resources
- Use CSS print media - Optimize CSS for print output