We closed this forum 18 June 2010. It has served us well since 2005 as the ALPHA forum did before it from 2002 to 2005. New discussions are ongoing at the new URL http://forum.processing.org. You'll need to sign up and get a new user account. We're sorry about that inconvenience, but we think it's better in the long run. The content on this forum will remain online.
IndexProgramming Questions & HelpOther Libraries › Write PDF to Byte[] (without writing a file!)
Page Index Toggle Pages: 1
Write PDF to Byte[] (without writing a file!) (Read 3665 times)
Write PDF to Byte[] (without writing a file!)
Mar 25th, 2009, 7:29am
 
I have an applet which uploads images to a web server, using code based on the example in hacks (http://processing.org/hacks/hacks:savetoweb). This works great for bitmaps - but can I upload a PDF file?

Also: I would rather not have to save files locally (which would mean signing the applet etc).

From what I can tell I need to be able to get at the PDF data as a byte array... but the PDF renderer wants to write a local file only. Any way I can get at the data without writing a file? Thanks,

Mitchell
Re: Write PDF to Byte[] (without writing a file!)
Reply #1 - Mar 25th, 2009, 12:54pm
 
That's a good question...

I just looked at the source of PGraphicsPDF class (in Processing source) and it seems, alas, that beginDraw is locked to use a file.

There is, BTW, a little bug: beginDraw() should ensure field 'file' is correctly initialized (either with the createGraphics() or the setPath() calls) and issue a meaningful error message (as it does in other circumstances). Currently, if we omit both, we get a (probably) puzzling NullPointerException.
Re: Write PDF to Byte[] (without writing a file!)
Reply #2 - Mar 25th, 2009, 5:55pm
 
Yes!
I managed to get the byte array without hacking too much, with standard Processing. With a little change of the PGraphicsPDF class, it would be painless.

Anyway, here is what I did: I took PGraphicsPDF source, and removed everything there, except beginDraw() (just removed the comments to keep listing short). I used that to make InMemoryPGraphicsPDF extending, of course, PGraphicsPDF.
I changed only the start of beginDraw(), to make it use any OutputStream, set as a field. Which I initialize in the allocate() callback.
And I added a getBytes() method which grabs the output stream as a byte array (supposing it is a ByteArrayOutputStream, of course).

Now, the nice thing is that Processing is able to compile my Java class with its package, allowing me to override the original class and access the package protected fields.
And the createGraphics is flexible enough to get any class, allowing custom behavior.

Here is my test code, and the custom PGraphics:
Code:
import processing.pdf.*;

PFont f;
int pageCount = 10;
int prevX;
PGraphics pdf;

void setup()
{
 size(400, 400);
 f = createFont("Arial", 72);
 prevX = width/2;
 pdf = createGraphics(400, 400, "processing.pdf.InMemoryPGraphicsPDF");
 pdf.beginDraw();
}

void draw()
{
 println("Making page " + frameCount);
 pdf.background(255);
 pdf.fill(#005500);
 pdf.stroke(#000055);
 pdf.strokeWeight(5);
 pdf.textFont(f, 72);
 int newX = int(random(0, width));
 // Draw something good here
 pdf.line(prevX, 0, newX, height);
 prevX = newX;
 pdf.text("Page " + frameCount, 100, 200);

 // When finished drawing, quit and save the file
 if (frameCount >= 10)
 {
   // Clean up phase
   pdf.dispose();
   pdf.endDraw();

   InMemoryPGraphicsPDF impgpdf = (InMemoryPGraphicsPDF) pdf;  // Get the real renderer
   byte[] pdfData = impgpdf.getBytes();
   println("Done: " + pdfData.length + " bytes");
   // Output file to disk, to examine it
   // Purpose is actually to serialize it over network
   try {
     FileOutputStream fos = new FileOutputStream("E:/temp/Output.pdf");
     BufferedOutputStream bos = new BufferedOutputStream(fos, 16384);
     bos.write(pdfData);
     bos.flush();
     bos.close();
   } catch (IOException ioe) {} // Not super clean but just ad hoc here
   
   exit();
 }
 else
 {
   PGraphicsPDF pdfg = (PGraphicsPDF) pdf;  // Get the renderer
   pdfg.nextPage();  // Tell it to go to the next page
 }
}


InMemoryPGraphicsPDF.java Code:
package processing.pdf;

import java.io.*;

import com.lowagie.text.*;
import com.lowagie.text.pdf.*;

import processing.core.*;
import processing.pdf.*;

/*
Various problems with PGraphicsPDF, perhaps to be fixed in a future version:
- File temp isn't used, should be removed.
- Fields are package protected. Perhaps they should be just 'protected'. It would ease overriding.
- beginDraw() doesn't check if 'file' field is not null. Should issue a meaningful error message if not.
- beginDraw() relies on 'file' existence.
Perhaps we should put:
       FileOutputStream fos = new FileOutputStream(file);
       BufferedOutputStream bos = new BufferedOutputStream(fos, 16384);
lines after 'file' creation and have a generic OutputStream as field, instead.
If beginDraw() uses this OutputStream, it would allow more flexibility in output.
*/
public class InMemoryPGraphicsPDF extends PGraphicsPDF
{
 protected OutputStream outStream;
 protected void allocate() // Called by PGraphics in setSize()
 {
   outStream = new ByteArrayOutputStream(16384);
 }
 public void beginDraw() {
   if (document == null) {
     document = new Document(new Rectangle(width, height));
     try {
       writer = PdfWriter.getInstance(document, outStream);
       document.open();
       content = writer.getDirectContent();
     } catch (Exception e) {
       e.printStackTrace();
       throw new RuntimeException("Problem writing the PDF data.");
     }
// Code below is unmodified code from PGraphicsPDF
     mapper = new DefaultFontMapper();
     if (PApplet.platform == PApplet.MACOSX) {
       try {
         String homeLibraryFonts =
           System.getProperty("user.home") + "/Library/Fonts";
         mapper.insertDirectory(homeLibraryFonts);
       } catch (Exception e) {
       }
       mapper.insertDirectory("/System/Library/Fonts");
       mapper.insertDirectory("/Library/Fonts");
     } else if (PApplet.platform == PApplet.WINDOWS) {
       File roots[] = File.listRoots();
       for (int i = 0; i < roots.length; i++) {
         if (roots[i].toString().startsWith("A:")) {
           continue;
         }
         File folder = new File(roots[i], "WINDOWS/Fonts");
         if (folder.exists()) {
           mapper.insertDirectory(folder.getAbsolutePath());
           break;
         }
         folder = new File(roots[i], "WINNT/Fonts");
         if (folder.exists()) {
           mapper.insertDirectory(folder.getAbsolutePath());
           break;
         }
       }
     }
     g2 = content.createGraphics(width, height, mapper);
   }
   super.beginDraw();
 }
 public byte[] getBytes() {
   return ((ByteArrayOutputStream) outStream).toByteArray();
 }
}

Of course, writing the byte array to disk has no interest, except for testing.
Re: Write PDF to Byte[] (without writing a file!)
Reply #3 - Mar 25th, 2009, 11:15pm
 
Wow, thanks a heap. I had started rummaging around in PgraphicsPDF but it would have taken me days of bafflement to get this far.

I'll try this in my applet and report back. Thanks!
Re: Write PDF to Byte[] (without writing a file!)
Reply #4 - Mar 30th, 2009, 7:36pm
 
Yep, works great. The applet I'm making draws to both screen and pdf, and I can simply switch renderers by switching which PGraphics object is being drawn to.

And the byte[] array slots nicely into the save to web function... so it all works!

I'll post the URL of the finished thing here for completion... and PhilLo, you'll get credit for this too - thanks again...
Re: Write PDF to Byte[] (without writing a file!)
Reply #5 - Mar 30th, 2009, 11:11pm
 
I've just made a Post To Web library that incorporates this, http://libraries.seltar.org

Enjoy Smiley
Re: Write PDF to Byte[] (without writing a file!)
Reply #6 - Mar 31st, 2009, 3:49am
 
I saw that, and I am flattered you used my code.
I saw I forgot my usual (on non-trivial code) header with credit and license notice on the PHP code. I updated at http://philho.pastebin.com/f55184692 (but I can no longer update old threads with the new forum...  Sad) You might want to use this version. Thanks.

Oh, and my pseudo is PhiLho (short for Philippe Lhoste!), not PhilLo. Wink
Re: Write PDF to Byte[] (without writing a file!)
Reply #7 - Apr 14th, 2009, 10:08pm
 
One more little wrinkle: PhiLho's code works perfectly in an applet, except that the FontMapper function throws a security error, because it is trying to read the local file system. I simply commented out everything between
mapper = new DefaultFontMapper(); and g2 = content.createGraphics(width, height, mapper); - in other words, it still creates the DefaultFontMapper and passes it in to createGraphics, but it doesn't try to sniff out fonts on the user's machine. It seems to work fine...

The completed project is for a new Masters course I'm running -  you can grow your own logotype and have it mailed to you in PDF form: http://creative.canberra.edu.au/mitchell/dd

There's a bit of documentation on the applet on my blog: http://teemingvoid.blogspot.com/2009/04/master-of-digital-design-grow-your-own.h...

Massive thanks again to PhiLho and Seltar for their code.
Re: Write PDF to Byte[] (without writing a file!)
Reply #8 - Apr 14th, 2009, 11:46pm
 
Added your credits and license notices now, PhiLho, and sorry for getting your nick wrong! Wink

seltar
Page Index Toggle Pages: 1