PDF export with createGraphics won't play nice with selectOutput file selector

I might be a bit over my head, apologies for the semi-long sketch and wall-of-text. I’m really puzzled by this!

I have a sketch with a PDF export that works great on it’s own, but that just saves a file in the sketch folder. So I wanted to add a file selector (selectOutput), but that turned out to be not so easy…

I’ve kind of narrowed it down (and kind of not). I found a work-around of sorts and made this small (well, small-ish) stand-alone sketch to demonstrate. It works some, maybe most of the time but not always. Not to mention that this work-around doesn’t work at all in my main sketch that I want to use it in.

The main thing to look at here is in the mouseClicked() function, and the two exportToPDF() functions.

(In my main sketch I don’t use mouseClicked(), nor have these button classes like here, so there’s a few differences).

In short, the work-around was to create a dummy PDF file before calling selectOutput, and then make the real PDF file. Otherwise I’d always get a NullPointerException (and usually several lines of errors or warnings). I still get the NPE some of the time.

If I cancel out of the file dialog, I also get several lines of warnings but the sketch continues.

I suspected some of this could be due to the path name string containing the backslash character, which was not escaped(\\), but it didn’t matter if I used the file path at all.

Hopefully someone have some idea what’s going on, or a fix… Maybe there’s something I’m completely ignorant of.

Here’s the demo sketch:

Click to expand
/*  PDF export with file selector fail test

    2020.08.16 raron
*/

import processing.pdf.*;
import static javax.swing.JOptionPane.*;

PGraphics originalG;
PGraphics pdf;

// PDF size
int pdfWidth = 2100;
int pdfHeight = 2970;
int pdfPages = 4; // nr. of test pages

File PDFfile = new File("New.pdf");

// Processing window size
int wWidth  = 400;
int wHeight = 300;

// The buttons
verySimpleButton greenButton;
verySimpleButton redButton;

// Just some colors
color whiteCol = color( 200 );
color blackCol = color( 0 );
color bgCol    = color(  24,  24,  24 );
color greenCol = color(   0,  80,   0 );
color redCol   = color(  80,   0,   0 );



void settings()
{
  size(wWidth, wHeight);
}



void setup()
{
  originalG = g; // Save Processing default graphics renderer (afaik)

  greenButton = new verySimpleButton(  66, 120, 100, 50, greenCol );
  redButton   = new verySimpleButton( 232, 120, 100, 50, redCol );
}



void draw()
{
  background( bgCol );
  fill( whiteCol );
  textSize( 16 );
  text("Press a button to attempt PDF export", 30, 75);
  textSize( 12 );
  text("Green button without file selector always works", 40, 210);
  text("Red button with file selector sometimes mess up", 40, 240);
  
  // Button updates
  greenButton.display();
  redButton.display();
}



void mouseClicked()
{
  // This works regardless of dummy pdf or not
  if (greenButton.underMouse()) exportToPDF("test.pdf");

  // This sometimes fail
  if (redButton.underMouse()) 
  {
    // Apparently, one has to make a dummy pdf before the file selection dialogue
    // Or else it always fails (NullPointerException)
    // It still fails sometimes, or makes a messed up pdf file mixed with the dummy pdf.
    pdf = createGraphics(pdfWidth, pdfHeight, PDF, "dummy.pdf");
    usePDF();
    fill(greenCol); // might help to do something with the dummy pdf?
    textSize( 80 );
    text("DUMMY PDF", 100, 100);
    g.dispose();
    useOriginalG();
    delay(200); // Seems to help?

    selectOutput("Save file as", "exportToPDF", PDFfile);
  }
}



void exportToPDF(File selection)
{
  String PDFPathAndFile = selection.getAbsolutePath();
  
  // Odd that this works to print without escaping the backslashes in the path strings (Windows)?
  println("Absolute path     : " + PDFPathAndFile);
  
  showMessageDialog(null, "Will a PDF be exported?", "Not yet done...", INFORMATION_MESSAGE);

  exportToPDF(PDFPathAndFile); 
}



void exportToPDF(String filePath)
{
  pdf = createGraphics(pdfWidth, pdfHeight, PDF, filePath);

  // Start PDF export
  usePDF();
  fill( blackCol );
  textSize( 50 );

  // Some demo pages
  for (int p=0; p<pdfPages; p++)
  {
    if( p>0 ) PDFnextPage();
    text("TEST PAGE " + (p+1), pdfWidth/2, pdfHeight/2);
  }
  
  // End PDF export
  g.dispose();
  useOriginalG();

  showMessageDialog(null, "PDF file exported!", "Done!", INFORMATION_MESSAGE);
}





// Draw to pdf
void usePDF()
{
  g.endDraw();
  g = pdf;
  g.beginDraw();
}


// Start next PDF page
void PDFnextPage()
{
  PGraphicsPDF pdfg = (PGraphicsPDF) pdf; // get the pdf renderer
  pdfg.nextPage();
}


// Draw to processing window
void useOriginalG()
{
  g.endDraw();
  g = originalG;
  g.beginDraw();
}


class verySimpleButton
{
  int x,y,w,h;
  color baseColor, hoverColor, pressColor, edgeColor;
  
  verySimpleButton(int _bx, int _by, int _bw, int _bh, color _baseColor)
  {
    x = _bx;
    y = _by;
    w = _bw;
    h = _bh;
    baseColor  = _baseColor;
    hoverColor = lerpColor(baseColor, color(255, 255, 255), 0.2);
    pressColor = lerpColor(baseColor, color(255, 255, 255), 0.4);
    edgeColor  = lerpColor(baseColor, color(255, 255, 255), 0.6);
  }
  
  void display()
  {
    fill(baseColor);
    if (underMouse())
    {
      fill(hoverColor);
      if(mousePressed) fill(pressColor);
    }
    stroke(edgeColor);
    rect(x,y,w,h);
  }
  
  boolean underMouse()
  {
    if (mouseX>x && mouseX<x+w && mouseY>y && mouseY<y+h) return true;
    return false;
  }
}
1 Like

New work-around based on the old, seems to work (even in my other, main sketch) :slightly_smiling_face:

Simple development of my other work-around: Make a pdf file in the sketch/data folder first, then copy it to where you want.

It requires to read in the whole file into a byte array and write it out again though (pretty soon I’ll need some duct tape and a paper clip for this thing to work :stuck_out_tongue:) Probably not the most efficient way but it works (so far).

Learned that “selectOutput()” is asynchronous, I guess you could say? I’m not used to multi-threading (old spaghetti monster here). It’s as if selectOutput don’t block execution of the rest of the function it’s in, until the function reaches it’s end. Then it waits for it? (void mouseClicked() in this instance. Or maybe it’s just because there aren’t more mouse clicks in the mean time?)

I don’t get why it fails when I put all the code in selectOutput’s callback function (which I thought, and still think, don’t execute until after it’s finished and a file is selected. I’m not sure what to make of this).

Cancelling the file selector produces a “java.lang.reflect.InvocationTargetException” warning, but the sketch continue to run.

Anyway, updated code:

Click to expand
/*  PDF export with file selector

    File byte copy method

    2020.08.17 raron
*/

import processing.pdf.*;
import static javax.swing.JOptionPane.*;

PGraphics originalG;
PGraphics pdf;

// PDF size
int pdfWidth = 2100;
int pdfHeight = 2970;
int pdfPages = 4; // nr. of test pages

File PDFfile = new File("new_document.pdf");

// Processing window size
int wWidth  = 400;
int wHeight = 300;

// The buttons
verySimpleButton greenButton;
verySimpleButton redButton;

// Just some colors
color whiteCol = color( 200 );
color blackCol = color( 0 );
color bgCol    = color(  24,  24,  24 );
color greenCol = color(   0,  80,   0 );
color redCol   = color(  80,   0,   0 );



void settings()
{
  size(wWidth, wHeight);
}



void setup()
{
  originalG = g; // Save Processing default graphics renderer (afaik)

  greenButton = new verySimpleButton(  66, 120, 100, 50, greenCol );
  redButton   = new verySimpleButton( 232, 120, 100, 50, redCol );
}



void draw()
{
  background( bgCol );
  fill( whiteCol );
  textSize( 16 );
  text("Press a button to attempt PDF export", 30, 75);
  textSize( 12 );
  text("Green button without file selector always works", 40, 210);
  text("but saves in the sketch folder", 50, 225);
  text("Red button now works", 40, 250);
  text("by simply copying a temp file from the sketch folder", 50, 265);
  
  // Button updates
  greenButton.display();
  redButton.display();
}



void mouseClicked()
{
  // This works regardless of dummy pdf or not
  // but it only saves in the sketch folder
  if (greenButton.underMouse())
  {
    exportToPDF("data/test.pdf");
    showMessageDialog(null, "PDF file exported", "Done!", INFORMATION_MESSAGE);
  }

  if (redButton.underMouse())
  {
    // Make temporary pdf file FIRST!
    exportToPDF("data/temp.pdf");
    
    // Then copy it to wherever
    selectOutput("Save file as", "copyPDFfile", PDFfile);
  }
  println("SelectOutput doesn't block executing the function it's in?");
  println("This is the end of the mouseClicked() function.");
}




void copyPDFfile(File selection)
{
  String PDFPathAndFile = selection.getAbsolutePath();
  
  // Odd that this works to print without escaping the backslashes in the path strings (Windows)?
  // (Guess it's escaped already?)
  println();
  println("Absolute path: " + PDFPathAndFile);
  println();
  
  // Copy the file
  byte pdfFile[] = loadBytes("temp.pdf");
  saveBytes(PDFPathAndFile, pdfFile);
  
  showMessageDialog(null, "PDF exported and copied", "Done!", INFORMATION_MESSAGE);
}




void exportToPDF(String filePath)
{
  pdf = createGraphics(pdfWidth, pdfHeight, PDF, filePath);

  // Start PDF export
  usePDF();
  fill( blackCol );
  textSize( 50 );

  // Some demo pages
  for (int p=0; p<pdfPages; p++)
  {
    if( p>0 ) PDFnextPage();
    text("TEST PAGE " + (p+1), pdfWidth/2, pdfHeight/2);
  }
  
  // End PDF export
  g.dispose();
  useOriginalG();
}





// Draw to pdf
void usePDF()
{
  g.endDraw();
  g = pdf;
  g.beginDraw();
}


// Start next PDF page
void PDFnextPage()
{
  PGraphicsPDF pdfg = (PGraphicsPDF) pdf; // get the pdf renderer
  pdfg.nextPage();
}


// Draw to processing window
void useOriginalG()
{
  g.endDraw();
  g = originalG;
  g.beginDraw();
}


class verySimpleButton
{
  int x,y,w,h;
  color baseColor, hoverColor, pressColor, edgeColor;
  
  verySimpleButton(int _bx, int _by, int _bw, int _bh, color _baseColor)
  {
    x = _bx;
    y = _by;
    w = _bw;
    h = _bh;
    baseColor  = _baseColor;
    hoverColor = lerpColor(baseColor,color(255,255,255),0.2);
    pressColor = lerpColor(baseColor,color(255,255,255),0.4);
    edgeColor  = lerpColor(baseColor,color(255,255,255),0.6);
  }
  
  void display()
  {
    fill(baseColor);
    if (underMouse())
    {
      fill(hoverColor);
      if(mousePressed) fill(pressColor);
    }
    stroke(edgeColor);
    rect(x,y,w,h);
  }
  
  boolean underMouse()
  {
    if (mouseX>x && mouseX<x+w && mouseY>y && mouseY<y+h) return true;
    return false;
  }
}
1 Like