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.