# printpdf **Repository Path**: jarkata/printpdf ## Basic Information - **Project Name**: printpdf - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-05-11 - **Last Updated**: 2025-05-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # printpdf [![CI](https://github.com/fschutt/printpdf/actions/workflows/ci.yaml/badge.svg)](https://github.com/fschutt/printpdf/actions/workflows/ci.yaml) [![Dependencies](https://deps.rs/repo/github/fschutt/printpdf/status.svg)](https://deps.rs/repo/github/fschutt/printpdf) `printpdf` is a Rust library for generating, reading (!), rendering (!!) and optimizing PDF documents. [Website](https://fschutt.github.io/printpdf) | [Crates.io](https://crates.io/crates/printpdf) | [Documentation](https://docs.rs/printpdf) | [Donate](https://github.com/sponsors/fschutt) > [!IMPORTANT] > HTML-to-PDF rendering is still experimental and WIP. > In doubt, position PDF elements manually instead. ## Features - Pages, Bookmarks, link annotations (read / write) - Layers (read / write) - Graphics: lines, shapes, bezier curves, SVG content (read / write) - Images encoding / decoding (read support: experimental / write supported with `image`) - Embedded fonts, Unicode support (read support: experimental / write) - Minifying file size (auto-subsetting fonts) - Rendering PDF pages to SVG files: experimental - Extracting text from PDF pages (with auto-decoding Unicode, line breaks, text positions: experimental) - Minimal HTML layouting for simple page layout (using `azul-layout` + `kuchiki` HTML parser) - Advanced graphics - overprint, blending, etc. - Advanced typography - character / word scaling and spacing, superscript, subscript, etc. - Embedding SVGs (uses `svg2pdf` crate internally) NOT supported are: - Gradients - Patterns - File attachements - Open Prepress Interface - Halftoning images - Conformance / error checking for various PDF standards - Embedded Javascript See the WASM32 demo live at: https://fschutt.github.io/printpdf ## Writing PDF ### Basic example ```rust use printpdf::*; fn main() { let mut doc = PdfDocument::new("My first PDF"); let page1_contents = vec![Op::Marker { id: "debugging-marker".to_string() }]; let page1 = PdfPage::new(Mm(10.0), Mm(250.0), page1_contents); let pdf_bytes: Vec = doc .with_pages(vec![page1]) .save(&PdfSaveOptions::default()); } ``` ### Graphics ```rust use printpdf::*; fn main() { let mut doc = PdfDocument::new("My first PDF"); let line = Line { // Quadratic shape. The "false" determines if the next (following) // point is a bezier handle (for curves) // If you want holes, simply reorder the winding of the points to be // counterclockwise instead of clockwise. points: vec![ (Point::new(Mm(100.0), Mm(100.0)), false), (Point::new(Mm(100.0), Mm(200.0)), false), (Point::new(Mm(300.0), Mm(200.0)), false), (Point::new(Mm(300.0), Mm(100.0)), false), ], is_closed: true, }; // Triangle shape let polygon = Polygon { rings: vec![vec![ (Point::new(Mm(150.0), Mm(150.0)), false), (Point::new(Mm(150.0), Mm(250.0)), false), (Point::new(Mm(350.0), Mm(250.0)), false), ]], mode: PaintMode::FillStroke, winding_order: WindingOrder::NonZero, }; // Graphics config let fill_color = Color::Cmyk(Cmyk::new(0.0, 0.23, 0.0, 0.0, None)); let outline_color = Color::Rgb(Rgb::new(0.75, 1.0, 0.64, None)); let mut dash_pattern = LineDashPattern::default(); dash_pattern.dash_1 = Some(20); let extgstate = ExtendedGraphicsStateBuilder::new() .with_overprint_stroke(true) .with_blend_mode(BlendMode::multiply()) .build(); let page1_contents = vec![ // add line1 (square) Op::SetOutlineColor { col: Color::Rgb(Rgb::new(0.75, 1.0, 0.64, None)) }, Op::SetOutlineThickness { pt: Pt(10.0) }, Op::DrawLine { line: line }, // add line2 (triangle) Op::SaveGraphicsState, Op::LoadGraphicsState { gs: doc.add_graphics_state(extgstate) }, Op::SetLineDashPattern { dash: dash_pattern }, Op::SetLineJoinStyle { join: LineJoinStyle::Round }, Op::SetLineCapStyle { cap: LineCapStyle::Round }, Op::SetFillColor { col: fill_color }, Op::SetOutlineThickness { pt: Pt(15.0) }, Op::SetOutlineColor { col: outline_color }, Op::DrawPolygon { polygon: polygon }, Op::RestoreGraphicsState, ]; let page1 = PdfPage::new(Mm(10.0), Mm(250.0), page1_contents); let pdf_bytes: Vec = doc .with_pages(vec![page1]) .save(&PdfSaveOptions::default()); } ``` ### Images - Images only get compressed in release mode. You might get huge PDFs (6 or more MB) in debug mode. - To make this process faster, use `BufReader` instead of directly reading from the file. - Scaling of images is implicitly done to fit one pixel = one dot at 300 dpi. ```rust use printpdf::*; fn main() { let mut doc = PdfDocument::new("My first PDF"); let image_bytes = include_bytes!("assets/img/BMP_test.bmp"); let image = RawImage::decode_from_bytes(image_bytes).unwrap(); // requires --feature bmp // In the PDF, an image is an `XObject`, identified by a unique `ImageId` let image_xobject_id = doc.add_image(image); let page1_contents = vec![ Op::UseXobject { id: image_xobject_id.clone(), transform: XObjectTransform::default() } ]; let page1 = PdfPage::new(Mm(10.0), Mm(250.0), page1_contents); let pdf_bytes: Vec = doc .with_pages(vec![page1]) .save(&PdfSaveOptions::default()); } ``` ### Fonts ```rust use printpdf::*; fn main() { let mut doc = PdfDocument::new("My first PDF"); let roboto_bytes = include_bytes!("assets/fonts/RobotoMedium.ttf").unwrap() let font_index = 0; let mut warnings = Vec::new(); let font = ParsedFont::from_bytes(&roboto_bytes, font_index, &mut warnings).unwrap(); // If you need custom text shaping (uses the `allsorts` font shaper internally) // let glyphs = font.shape(text); // printpdf automatically keeps track of which fonts are used in the PDF let font_id = doc.add_font(&font); let text_pos = Point { x: Mm(10.0).into(), y: Mm(100.0).into(), }; // from bottom left let page1_contents = vec![ Op::SetLineHeight { lh: Pt(33.0) }, Op::SetWordSpacing { pt: Pt(33.0) }, Op::SetCharacterSpacing { multiplier: 10.0 }, Op::SetTextCursor { pos: text_pos }, // Op::WriteCodepoints { ... } // Op::WriteCodepointsWithKerning { ... } Op::WriteText { items: vec![TextItem::Text("Lorem ipsum".to_string())], font: font_id.clone(), }, Op::AddLineBreak, Op::WriteText { items: vec![TextItem::Text("dolor sit amet".to_string())], font: font_id.clone(), }, Op::AddLineBreak, ]; let save_options = PdfSaveOptions { subset_fonts: true, // auto-subset fonts on save ..Default::default() }; let page1 = PdfPage::new(Mm(10.0), Mm(250.0), page1_contents); let mut warnings = Vec::new(); let pdf_bytes: Vec = doc .with_pages(vec![page1]) .save(&save_options, &mut warnings); } ``` ### Tables, HTML For creating tables, etc. printpdf uses a basic layout system, similar to wkhtmltopdf (although more limited in terms of features). It's good enough for basic page layouting, book rendering and reports / forms / etc. Includes automatic page-breaking. Since printpdf supports WASM, there is an interactive demo at https://fschutt.github.io/printpdf - try playing with the XML. See [SYNTAX.md](./SYNTAX.md) for the XML syntax description. ```rust // needs --features="html" use printpdf::*; fn main() { // See https://fschutt.github.io/printpdf for an interactive WASM demo! let html = r#"

Hello!

World!

Chapter {attr:chapter} * {attr:subsection}

{builtin:pagenum}