PDF Export

The PDF library makes it possible to write PDF files directly from Processing. These vector graphics files can be scaled to any size and output at very high resolutions. The PDF library can flatten 3D data into a 2D vector file, but to export 3D data, use the DXF library. The source code is available on the Processing GitHub repository. Please report bugs here.

This library is frequently used with the core Processing function size(), with a combination of beginRecord() and endRecord(), or with beginRaw() and endRaw(). The createGraphics() function can also be useful. See the examples below for different techniques.

Single Frame (No Screen Display)

This example draws a single frame to a PDF file and quits. (Note that no display window will open; this helps when you're trying to create massive PDF images that are far larger than the screen size.)

import processing.pdf.*;

void setup() {
  size(400, 400, PDF, "filename.pdf");
}

void draw() {
  // Draw something good here
  line(0, 0, width/2, height);

  // Exit the program
  println("Finished.");
  exit();
}

Multiple Pages (No Screen Display)

It's possible to write every frame as a new page in the PDF document. This example creates a 100 page document:

import processing.pdf.*;

void setup() {
  size(400, 400, PDF, "filename.pdf");
}

void draw() {
  // Draw something good here
  line(0, 0, frameCount * 4, height);

  PGraphicsPDF pdf = (PGraphicsPDF) g;  // Get the renderer

  // When finished drawing, quit and save the file
  if (frameCount == 100) {
    exit();
  } else {
    pdf.nextPage();  // Tell it to go to the next page
  }
}

Single Frame (With Screen Display)

To draw to the screen while also saving a PDF, use the beginRecord() and endRecord() functions. This is slower, but is useful when you need to see what you're working on as it saves.

import processing.pdf.*;

void setup() {
  size(400, 400);
  noLoop();
  beginRecord(PDF, "filename.pdf");
}

void draw() {
  // Draw something good here
  line(0, 0, width/2, height);

  endRecord();
}

Single Frame from an Animation (With Screen Display)

It's also possible to save one frame from a program with moving elements. Create a boolean variable to turn the PDF recording process on and off

import processing.pdf.*;

boolean record;

void setup() {
  size(400, 400);
}

void draw() {
  if (record) {
    // Note that #### will be replaced with the frame number. Fancy!
    beginRecord(PDF, "frame-####.pdf");
  }

  // Draw something good here
  background(255);
  line(mouseX, mouseY, width/2, height/2);

  if (record) {
    endRecord();
  record = false;
  }
}

// Use a keypress so thousands of files aren't created
void mousePressed() {
  record = true;
}

Many Frames Into One File (With Screen Display)

The following example records absolutely everything that happens while a sketch is running. Hitting the 'q' key will quit the sketch. The sketch calls exit(), which is necessary to make sure that the file is properly written when complete.

import processing.pdf.*;

void setup() {
  size(400, 400);
  beginRecord(PDF, "everything.pdf");
}

void draw() {
  // Be sure not to call background, otherwise your file
  // will just accumulate lots of mess, only to become invisible

  // Draw something good here
  line(mouseX, mouseY, width/2, height/2);
}

void keyPressed() {
  if (key == 'q') {
    endRecord();
    exit();
  }
}

Pausing While Recording (With Screen Display)

It's also possible to pause/resume recording along the way. The following example toggles recording on/off each time you hit the 'r' key. Pressing the 'q' key will quit the sketch.

import processing.pdf.*;

boolean recording;
PGraphicsPDF pdf;

void setup() {
  size(400, 400);
  pdf = (PGraphicsPDF) createGraphics(width, height, PDF, "pause-resume.pdf");
}

void draw() {
  // Be sure not to call background, otherwise your file
  // will just accumulate lots of mess, only to become invisible

  // Draw something good here
  if (mousePressed) {
    line(pmouseX, pmouseY, mouseX, mouseY);
  }
}

void keyPressed() {
  if (key == 'r') {
    if (recording) {
      endRecord();
      println("Recording stopped.");
      recording = false;
    } else {
      beginRecord(pdf);
      println("Recording started.");
      recording = true;
    }
  } else if (key == 'q') {
    if (recording) {
      endRecord();
    }
    exit();
  }
}

PDF Files from 3D Geometry (With Screen Display)

To create vectors from 3D data, use the beginRaw() and endRaw() commands. These commands will grab the shape data just before it is rendered to the screen. At this stage, your entire scene is nothing but a long list of lines and triangles. This means that a shape created with sphere() method will be made up of hundreds of triangles, rather than a single object.

When using beginRaw() and endRaw(), it's possible to write to either a 2D or 3D renderer. For instance, beginRaw() with the PDF library will write the geometry as flattened triangles and lines.

import processing.pdf.*;

boolean record;

void setup() {
  size(500, 500, P3D);
}

void draw() {
  if (record) {
    beginRaw(PDF, "output.pdf");
  }

  // Do all your drawing here
  background(204);
  translate(width/2, height/2, -200);
  rotateZ(0.2);
  rotateY(mouseX/500.0);
  box(200);

  if (record) {
    endRaw();
    record = false;
  }
}

// Hit 'r' to record a single frame
void keyPressed() {
  if (key == 'r') {
    record = true;
  }
}

Using createGraphics() to Create a PDF File

To write a PDF file using only the createGraphics() command, rather than as part of a sketch, it's necessary to call dispose() on the PGraphicsPDF object. This is the same as calling exit(), but it won't quit the sketch.

import processing.pdf.*;

PGraphics pdf = createGraphics(300, 300, PDF, "output.pdf");
pdf.beginDraw();
pdf.background(128, 0, 0);
pdf.line(50, 50, 250, 250);
pdf.dispose();
pdf.endDraw();

Additional notes for the PDF renderer:

If you want 3D data, use the DXF recording library instead.

  • Using hint(ENABLE_DEPTH_SORT) can improve the appearance of 3D geometry drawn to 2D file formats.

  • Images don't look great, mostly because of the difference of expectations in how a PDF should look (scalable and high res) versus what happens when image data is written to it at 72 dpi.

  • Starting in release 0120, text is no longer treated as shape data by default, meaning that the font will need to be installed to view the PDF that's created. The upside is that the PDF will render better. - To force text to be treated as shape data, use textMode(SHAPE), immediately after size().

  • Another option for type, is to use createFont() with a TrueType font (some OpenType fonts also work). Any font that shows up in PFont.list() should work, or if not, adding a .ttf file to the data directory and calling createFont("fontname.ttf") will also work.

  • If loadFont() is used instead of createFont(), the text may show up bitmapped and ugly. In some cases, hint(ENABLE_NATIVE_FONTS) may improve the situation if the font is installed on the machine.

  • Starting a new page is a matter of calling nextPage() like so:

    PGraphicsPDF pdf = (PGraphicsPDF) g;
    pdf.nextPage();
    

    (This example is also shown above)

  • Again, exit() is really important when using PDF with size().