All formats

printpdf

LanguageRust
LicenseMIT
Repositorygithub.com/fschutt/printpdf
Documentationdocs.rs/printpdf, live WASM demo
Depends onpure Rust crates (lopdf, allsorts for fonts, azul-layout for HTML, optional image and svg2pdf)
Read / writebasic (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#

Shell
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#

Rust
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#

Rust
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#

Rust
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#

Rust
// 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#

Rust
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#