printpdf
| Language | Rust |
|---|---|
| License | MIT |
| Repository | github.com/fschutt/printpdf |
| Documentation | docs.rs/printpdf, live WASM demo |
| Depends on | pure Rust crates (lopdf, allsorts for fonts, azul-layout for HTML, optional image and svg2pdf) |
| Read / write | basic (parsing and experimental text extraction) / yes |
printpdf creates PDFs from Rust: you build pages from a list of operations (Op): text sections, fonts, colors, lines, polygons, images (XObjects), SVG, layers, bookmarks and links. Fonts can be the 14 built-in PDF fonts or embedded TrueType/OpenType fonts, which are subset automatically on save. An HTML mode (html feature, enabled by default) lays out simple HTML and CSS with automatic page breaks, and the crate compiles to WebAssembly.
Since version 0.8 the API is built around PdfDocument, PdfPage and Op, so tutorials written for 0.7 and older don’t apply. Parsing existing PDFs and extracting text are newer, experimental features; for reading or editing existing files use lopdf or pdfium-render.
Installation#
cargo add printpdf
# image decoding is an optional feature per format; HTML layout is on by default
cargo add printpdf --features png,jpeg
Basic usage: create a PDF with text#
use printpdf::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut doc = PdfDocument::new("Hello");
let ops = vec![
Op::StartTextSection,
// origin is the bottom-left corner of the page
Op::SetTextCursor { pos: Point { x: Mm(20.0).into(), y: Mm(270.0).into() } },
Op::SetFont { font: PdfFontHandle::Builtin(BuiltinFont::HelveticaBold), size: Pt(24.0) },
Op::SetLineHeight { lh: Pt(30.0) },
Op::ShowText { items: vec![TextItem::Text("Hello, PDF!".to_string())] },
Op::AddLineBreak,
Op::SetFont { font: PdfFontHandle::Builtin(BuiltinFont::Helvetica), size: Pt(12.0) },
Op::ShowText { items: vec![TextItem::Text("Created with printpdf.".to_string())] },
Op::EndTextSection,
];
let page = PdfPage::new(Mm(210.0), Mm(297.0), ops); // A4
let mut warnings = Vec::new();
let bytes = doc.with_pages(vec![page]).save(&PdfSaveOptions::default(), &mut warnings);
std::fs::write("hello.pdf", bytes)?;
Ok(())
}
Text-showing operations must be between StartTextSection and EndTextSection.
How to embed a TrueType font for Unicode text#
use printpdf::*;
let font_bytes = std::fs::read("DejaVuSans.ttf")?;
let mut doc = PdfDocument::new("Unicode");
let mut font_warnings = Vec::new();
let font = ParsedFont::from_bytes(&font_bytes, 0, &mut font_warnings).ok_or("cannot parse font")?;
let font_id = doc.add_font(&font);
let ops = vec![
Op::StartTextSection,
Op::SetTextCursor { pos: Point { x: Mm(20.0).into(), y: Mm(270.0).into() } },
Op::SetFont { font: PdfFontHandle::External(font_id.clone()), size: Pt(14.0) },
Op::ShowText { items: vec![TextItem::Text("Zażółć gęślą jaźń · Привет".to_string())] },
Op::EndTextSection,
];
let page = PdfPage::new(Mm(210.0), Mm(297.0), ops);
let options = PdfSaveOptions { subset_fonts: true, ..Default::default() };
let mut warnings = Vec::new();
std::fs::write("unicode.pdf", doc.with_pages(vec![page]).save(&options, &mut warnings))?;
How to draw lines and shapes#
let line = Line {
points: vec![
LinePoint { p: Point::new(Mm(20.0), Mm(260.0)), bezier: false },
LinePoint { p: Point::new(Mm(190.0), Mm(260.0)), bezier: false },
],
is_closed: false,
};
let ops = vec![
Op::SetOutlineColor { col: Color::Rgb(Rgb::new(0.0, 0.0, 0.5, None)) },
Op::SetOutlineThickness { pt: Pt(1.0) },
Op::DrawLine { line },
];
Op::DrawPolygon fills and strokes closed shapes; Op::SaveGraphicsState / Op::RestoreGraphicsState scope color and line settings.
How to add an image#
// needs the feature for the image format, e.g. "png" or "jpeg"
let bytes = std::fs::read("logo.png")?;
let mut warnings = Vec::new();
let image = RawImage::decode_from_bytes(&bytes, &mut warnings)?;
let image_id = doc.add_image(&image);
let ops = vec![Op::UseXobject { id: image_id, transform: XObjectTransform::default() }];
Images are placed at one pixel per dot at 300 DPI unless you set a scale in XObjectTransform.
How to extract text from an existing PDF#
let data = std::fs::read("in.pdf")?;
let mut warnings = Vec::new();
let doc = PdfDocument::parse(&data, &PdfParseOptions::default(), &mut warnings)?;
for (i, page_text) in doc.extract_text().iter().enumerate() {
println!("--- page {} ---\n{}", i + 1, page_text.join("\n"));
}
Tips#
- Build in release mode: images are compressed only in release builds, so debug output can be very large.
doc.add_graphics_state(ExtendedGraphicsState::default().with_blend_mode(...))adds transparency and blend modes.PdfDocument::from_html(featurehtml) generates paginated documents from simple HTML with headers and footers.