Render JUCE screenshots to pdf on OSX

Instead of the usual feature requests/nagging etc I for once have some code to share. It's something that works on OSX only, but if your project is crossplatform, which appears one major appeal of JUCE, this might help you. It's a way to produce vector screenshot from JUCE. There is a STUB-postscript renderer class in JUCE, but it is very incomplete and I needed a way to have resolution-independent screenshots for my project.

From past experience (with VSTGUI3) I remembered CoreGraphics has the ability to render everything to a PDF context. It turns out this can easily be used with JUCE on OSX and the result is a .pdf file with text-searchable context for any Component. I end up with this in my code to create a .pdf at (String)filepath:


    ScopedPointer<LowLevelGraphicsContext> renderer = createPDFRenderer(filepath, getWidth(), getHeight());
    Graphics g(*renderer);
    paintEntireComponent(g, false);
    flushPDFRenderer(renderer);

here are pastebins for the needed code (done with terrible static code because I had troubles subclassing the CoreGraphics renderer. Ideally this would be part of the CoreGraphics renderer, but I really do not like to change JUCE library files)...

OSXPDFRenderer.h: http://pastebin.com/Tcxdws7s   
OSXPDFRenderer.mm: http://pastebin.com/JwnKMgQt
 

the nasty bool PDFRendererIsActive(); call can be used to disable any bitmap caching etc. in order to create a true vector pdf.

Good tip, thanks! Pity it's OSX-specific, that'd be a neat feature to have on all platforms.

Hi there,

pretty useful!

I stubled upon a weird bug when gradients are used when painting:

The void* info pointer is null in the call to 
void CoreGraphicsContext::SavedState::gradientCallback (void* info, const CGFloat* inData, CGFloat* outData)

It gets called indirectly when CGContextEndPage(context); is invoked (in flushPDFRenderer)

Is it possible, that the evaluation of the gradients is deferred when using the pdf-renderer until CGContextEndPage() is called and that the SavedState-objects do no longer exist?

any ideas?
-- Benjamin

 

uuh. I wouldn't know. That's something that would need to be asked to Apple in one of their forums. I'm pretty sure many things get deferred until CGContextEndPage() is called. I'm not using gradients so I did not run into this problem. If the problem is what you think it is, then some changes would be needed to the JUCE CoreGraphics renderer to keep those info structures around to make this work. Ideally this whole pdf rendering would be part of the coregraphics renderer anyway instead of the nasty stuff I did with static variables.

Quoting:

https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CGFunction/index.html#//apple_ref/c/func/CGFunctionCreate

info: A pointer to user-defined storage for data that you want to pass to your callbacks. You need to make sure that the data persists for as long as it’s needed, which can be beyond the scope in which the Quartz function is used.

So I guess is, that my suspicion was right. The PDF renderer wont work with gradients without modifying juce.

Also from the Apple-Docs: "An alternative to using a CGShading object is to use the CGGradientRef opaque type. For applications that run in OS X v10.5 and later, CGGradient objects are much simpler to use. (See CGGradient Reference.)"

I'll give this a try…
-- Benjamin

Hi,

I modified the CoreGraphics renderer, so that it is using CGContextDrawLinearGradient instead of CGShading. And it works. The PDFRenderer no longer crashes when CGContextEndPage is called.

here's a patch: https://gist.github.com/beschulz/36cca0c38302bcd05618

@Jules: Is it possible to get this patch reviewed and merged? I'm not quite sure, what's the best way to check for 10.5+. I'm also not sure, why you chose to stick with CGShade. Presumably, because of backwards compatibility ?

best
-- Benjamin

 

For those playing along at home: the latest tip contains the changes. Which means exporting to PDF now also works with gradients.