• Stars
    star
    790
  • Rank 57,622 (Top 2 %)
  • Language
    Rust
  • License
    MIT License
  • Created over 7 years ago
  • Updated 3 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An easy-to-use library for writing PDF in Rust

printpdf

Travis CI Appveyor Dependencies

printpdf is a library designed for creating printable PDF documents.

Crates.io | Documentation

[dependencies]
printpdf = "0.5.0"

Features

Currently, printpdf can only write documents, not read them.

  • Page generation
  • Layers (Illustrator like layers)
  • Graphics (lines, shapes, bezier curves)
  • Images (currently BMP/PNG/JPG only or generate your own images)
  • Embedded fonts (TTF and OTF) with Unicode support
  • Advanced graphics - overprint control, blending modes, etc.
  • Advanced typography - character scaling, character spacing, superscript, subscript, outlining, etc.
  • PDF layers (you should be able to open the PDF in Illustrator and have the layers appear)

Getting started

Writing PDF

Simple page

use printpdf::*;
use std::fs::File;
use std::io::BufWriter;

let (doc, page1, layer1) = PdfDocument::new("PDF_Document_title", Mm(247.0), Mm(210.0), "Layer 1");
let (page2, layer1) = doc.add_page(Mm(10.0), Mm(250.0),"Page 2, Layer 1");

doc.save(&mut BufWriter::new(File::create("test_working.pdf").unwrap())).unwrap();

Adding graphical shapes

use printpdf::*;
use std::fs::File;
use std::io::BufWriter;
use std::iter::FromIterator;

let (doc, page1, layer1) = PdfDocument::new("printpdf graphics test", Mm(297.0), Mm(210.0), "Layer 1");
let current_layer = doc.get_page(page1).get_layer(layer1);

// 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.
let points1 = 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 the shape stroked? Is the shape closed? Is the shape filled?
let line1 = Line {
    points: points1,
    is_closed: true,
    has_fill: true,
    has_stroke: true,
    is_clipping_path: false,
};

// Triangle shape
// Note: Line is invisible by default, the previous method of
// constructing a line is recommended!
let mut line2 = Line::from_iter(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)]);

line2.set_stroke(true);
line2.set_closed(false);
line2.set_fill(false);
line2.set_as_clipping_path(false);

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);

current_layer.set_fill_color(fill_color);
current_layer.set_outline_color(outline_color);
current_layer.set_outline_thickness(10.0);

// Draw first line
current_layer.add_shape(line1);

let fill_color_2 = Color::Cmyk(Cmyk::new(0.0, 0.0, 0.0, 0.0, None));
let outline_color_2 = Color::Greyscale(Greyscale::new(0.45, None));

// More advanced graphical options
current_layer.set_overprint_stroke(true);
current_layer.set_blend_mode(BlendMode::Seperable(SeperableBlendMode::Multiply));
current_layer.set_line_dash_pattern(dash_pattern);
current_layer.set_line_cap_style(LineCapStyle::Round);

current_layer.set_fill_color(fill_color_2);
current_layer.set_outline_color(outline_color_2);
current_layer.set_outline_thickness(15.0);

// draw second line
current_layer.add_shape(line2);

Adding images

Note: Images only get compressed in release mode. You might get huge PDFs (6 or more MB) in debug mode. In release mode, the compression makes these files much smaller (~ 100 - 200 KB).

To make this process faster, use BufReader instead of directly reading from the file. Images are currently not a top priority.

Scaling of images is implicitly done to fit one pixel = one dot at 300 dpi.

extern crate printpdf;

// imports the `image` library with the exact version that we are using
use printpdf::*;

use std::convert::From;
use std::fs::File;

fn main() {
    let (doc, page1, layer1) = PdfDocument::new("PDF_Document_title", Mm(247.0), Mm(210.0), "Layer 1");
    let current_layer = doc.get_page(page1).get_layer(layer1);

    // currently, the only reliable file formats are bmp/jpeg/png
    // this is an issue of the image library, not a fault of printpdf
    let mut image_file = File::open("assets/img/BMP_test.bmp").unwrap();
    let image = Image::try_from(image_crate::codecs::bmp::BmpDecoder::new(&mut image_file).unwrap()).unwrap();

    // translate x, translate y, rotate, scale x, scale y
    // by default, an image is optimized to 300 DPI (if scale is None)
    // rotations and translations are always in relation to the lower left corner
    image.add_to_layer(current_layer.clone(), ImageTransform::default());

    // you can also construct images manually from your data:
    let mut image_file_2 = ImageXObject {
        width: Px(200),
        height: Px(200),
        color_space: ColorSpace::Greyscale,
        bits_per_component: ColorBits::Bit8,
        interpolate: true,
        /* put your bytes here. Make sure the total number of bytes =
           width * height * (bytes per component * number of components)
           (e.g. 2 (bytes) x 3 (colors) for RGB 16bit) */
        image_data: Vec::new(),
        image_filter: None, /* does not work yet */
        clipping_bbox: None, /* doesn't work either, untested */
    };

    let image2 = Image::from(image_file_2);
}

Adding fonts

Note: Fonts are shared between pages. This means that they are added to the document first and then a reference to this one object can be passed to multiple pages. This is different to images, for example, which can only be used once on the page they are created on (since that's the most common use-case).

use printpdf::*;
use std::fs::File;

let (doc, page1, layer1) = PdfDocument::new("PDF_Document_title", Mm(247.0), Mm(210.0), "Layer 1");
let current_layer = doc.get_page(page1).get_layer(layer1);

let text = "Lorem ipsum";
let text2 = "unicode: стуфхfцчшщъыьэюя";

let font = doc.add_external_font(File::open("assets/fonts/RobotoMedium.ttf").unwrap()).unwrap();
let font2 = doc.add_external_font(File::open("assets/fonts/RobotoMedium.ttf").unwrap()).unwrap();

// text, font size, x from left edge, y from bottom edge, font
current_layer.use_text(text, 48.0, Mm(200.0), Mm(200.0), &font);

// For more complex layout of text, you can use functions
// defined on the PdfLayerReference
// Make sure to wrap your commands
// in a `begin_text_section()` and `end_text_section()` wrapper
current_layer.begin_text_section();

    // setup the general fonts.
    // see the docs for these functions for details
    current_layer.set_font(&font2, 33.0);
    current_layer.set_text_cursor(Mm(10.0), Mm(10.0));
    current_layer.set_line_height(33.0);
    current_layer.set_word_spacing(3000.0);
    current_layer.set_character_spacing(10.0);
    current_layer.set_text_rendering_mode(TextRenderingMode::Stroke);

    // write two lines (one line break)
    current_layer.write_text(text.clone(), &font2);
    current_layer.add_line_break();
    current_layer.write_text(text2.clone(), &font2);
    current_layer.add_line_break();

    // write one line, but write text2 in superscript
    current_layer.write_text(text.clone(), &font2);
    current_layer.set_line_offset(10.0);
    current_layer.write_text(text2.clone(), &font2);

current_layer.end_text_section();

Changelog

See the CHANGELOG.md file.

Further reading

The PdfDocument is hidden behind a PdfDocumentReference, which locks the things you can do behind a facade. Pretty much all functions operate on a PdfLayerReference, so that would be where to look for existing functions or where to implement new functions. The PdfDocumentReference is a reference-counted document. It uses the pages and layers for inner mutablility, because I ran into borrowing issues with the document. IMPORTANT: All functions that mutate the state of the document, "borrow" the document mutably for the duration of the function. It is important that you don't borrow the document twice (your program will crash if you do so). I have prevented this wherever possible, by making the document only public to the crate so you cannot lock it from outside of this library.

Images have to be added to the pages resources before using them. Meaning, you can only use an image on the page that you added it to. Otherwise, you may end up with a corrupt PDF.

Fonts are embedded using freetype. There is a rusttype branch in this repository, but rusttype does fails to get the height of an unscaled font correctly, so that's why you currently have to use freetype

Please report issues if you have any, especially if you see BorrowMut errors (they should not happen). Kerning is currently not done, because neither freetype nor rusttype can reliably read kerning data. However, "correct" kerning / placement requires a full font shaping engine, etc. This would be a completely different project.

For learning how a PDF is actually made, please read the wiki (currently not completely finished). When I began making this library, these resources were not available anywhere, so I hope to help other people with these topics. Reading the wiki is essential if you want to contribute to this library.

Goals and Roadmap

The goal of printpdf is to be a general-use PDF library, such as libharu or similar. PDFs generated by printpdf should always adhere to a PDF standard, except if you turn it off. Currently, only the standard PDF/X-3:2002 is covered (i.e. valid PDF according to Adobe Acrobat). Over time, there will be more standards supported. Checking a PDF for errors is currently only a stub.

Planned features / Not done yet

The following features aren't implemented yet, most

  • Clipping
  • Aligning / layouting text
  • Open Prepress Interface
  • Halftoning images, Gradients, Patterns
  • SVG / instantiated content
  • Forms, annotations
  • Bookmarks / Table of contents
  • Conformance / error checking for various PDF standards
  • Embedded Javascript
  • Reading PDF
  • Completion of printpdf wiki

Testing

Currently the testing is pretty much non-existent, because PDF is very hard to test. This should change over time: Testing should be done in two stages. First, test the individual PDF objects, if the conversion into a PDF object is done correctly. The second stage is manual inspection of PDF objects via Adobe Preflight.

Put the tests of the first stage in /tests/mod.rs. The second stage tests are better to be handled inside the plugins' mod.rs file. printpdf depends highly on lopdf, so you can either construct your test object against a real type or a debug string of your serialized type. Either way is fine - you just have to check that the test object is conform to what PDF expects.

Useful links

Here are some resources I found while working on this library:

PDFXPlorer, shows the DOM tree of a PDF, needs .NET 2.0

Official PDF 1.7 reference

[GERMAN] How to embed unicode fonts in PDF

PDF X/1-a Validator

PDF X/3 technical notes

More Repositories

1

azul

Desktop GUI Framework
Rust
5,873
star
2

mystery-of-the-blend-backup

Backup repo of the "Mystery of the blend article"
50
star
3

rust-fontconfig

Pure-Rust rewrite of the Linux fontconfig library (no system dependencies) - using ttf-parser and allsorts
Rust
40
star
4

fastblur

Fast (linear time) implementation of the Gaussian Blur algorithm in Rust
Rust
39
star
5

moment-shadow-mapping

Moment shadow mapping walkthrough (description only)
26
star
6

proj5

Coordinate projection library
Rust
25
star
7

wtaskman

Task manager
Rust
13
star
8

polyclip

Library for efficient boolean operations on polygons in Rust
Rust
12
star
9

xlib-programming-rust

Minimal Xlib / OpenGL programming examples in Rust
RenderScript
9
star
10

dbflib

DBF library for reading and writing dBase III to dBase VII files. Only .dbf file support.
C++
8
star
11

LudumDare40

Ludum Dare 40
Rust
5
star
12

lua-jit-sys

System bindings for LuaJIT
C
5
star
13

vlc-static-rs

Modified vlc-rs fork that uses DLL loading to create Rust bindings for libvlc
Rust
5
star
14

clipboard2

Improved clipboard handling (similar to rust-clipboard)
Rust
5
star
15

wacom-sys

Bindgen-generated Rust bindings for libwacom 0.18.1
Rust
5
star
16

clipper-rs

Rust port of the clipper library - http://www.angusj.com/delphi/clipper.php
C++
5
star
17

quadtree-f32

Very simple, no-dependency quadtree implementation
Rust
4
star
18

simplify-rs

Line / Bezier simplification algorithm
Rust
4
star
19

opengl-software-renderer

NOT MY CODE! very old SGI code of an OpenGL software renderer, just for backup purposes
C
4
star
20

material-icons

Rust bindings (codepoint mappings) for the Google Material Icons font (https://material.io/tools/icons/)
Rust
4
star
21

polylabel-mini

Minimal fork of polylabel-rs with 0 dependencies
Rust
3
star
22

wacom

Rust
3
star
23

marching-squares

Parallelized marching squares algorithm for constructing closed isolines / contour lines
Rust
3
star
24

tesseract-static-rs

Library to link leptonica / tesseract statically into Rust binaries for easy distribution
Rust
3
star
25

gsr-jit

Test JIT compiler
Rust
3
star
26

street_index

Street index to CSV converter
Rust
3
star
27

pygame3-colors

HTML
2
star
28

servo_gui_test

A test of embedding the servo rendering engine as a desktop GUI
CSS
2
star
29

mdedit

Markdown editor using azul - test project
Rust
2
star
30

wkb-raster

Library to serialize raster data into PostGIS well-known-binary (WKB) format
Rust
2
star
31

beziercurve-wkt

Extension to serialize / deserialize bezier curves to and from strings, in a WKT-like format
Rust
2
star
32

layout2d

Preliminary GridBag / Flex UI system for Rust
Rust
2
star
33

libloading-mini

Micro version of libloading that only calls dlopen / dlsym / dlclose. No error checking, singlethreaded only.
Rust
2
star
34

wasmtest

Execute a Python script with wasmer
Rust
1
star
35

dockerdeploy-actix

Test repo to learn how to deploy an example actix server to Docker Hub using GitHub Actions
Dockerfile
1
star
36

nuke-dir

Rust
1
star
37

tesseract-wasmer

Port of tesseract.wasm to run without JS using Wasmer in Rust
JavaScript
1
star
38

mapedit-old

The old C++ source for mapedit, an X11-based map editing application
C++
1
star
39

azul.rs

azul.rs website
HTML
1
star