Friday, May 25, 2012

Quartz (1)

The next examples I want to do will be about drawing to a pdf in OS X. Currently, I've been compiling code and executing it in Terminal, to try to abstract the essentials away from all the heavy machinery of Xcode.

If we were using Xcode, and had an app with a GUI, then this could be as simple as calling a method of NSView:

NSRect r = [myView bounds];
NSData *data = [myView dataWithPDFInsideRect:r];
[data writeToFile:[sheet filename] atomically:YES];

But I wanted to write data to a pdf from a command line application. That led me to an example that used:


as well as a family of functions to create a CFURLRef:


These are functions, not methods of Objective-C objects. We're really in C-land here. The CF stands for Core Foundation. One aspect of this that I don't understand is that the docs talk about e.g. a CFURL and CFString, but the actual types are CFURLRef and CFStringRef. I'm not sure why yet.

CFURLCreateWithFileSystemPath takes 4 arguments:

CFURLRef CFURLCreateWithFileSystemPath (
CFAllocatorRef allocator,
CFStringRef filePath,
CFURLPathStyle pathStyle,
Boolean isDirectory

If you want to be fancy, you can pass kCFAllocatorDefault for the allocator, but this is really equivalent to NULL, and just tells the system to use the default allocator. The second argument is a CFStringRef. See the code example below for how to make one of these. The third argument should be kCFURLPOSIXPathStyle (other options would be kCFURLHFSPathStyle and kCFURLWindowsPathStyle). And the last argumenet is false, because this is not for a directory.

Since the function has "Create" in the name, we "own" the resulting object, and should release it when we don't need it anymore.

However, it turns out to be much simpler than this (although the above works). NSString and NSURL are "toll-free bridged" with CFStringRef and CFURLRef (docs), and can be substituted one for another in any function call.

It took me far too long to discover the secret of this (as the link above shows), you can substitute, but you must also cast, as in (CFURLRef)url.

Finally, having a CFURLRef, we can get the graphics context appropriate for drawing to a pdf. We bracket the drawing code with calls to CGContextBeginPage and CGContextEndPage. The drawing itself is the Apple example and self-explanatory, except that it's important that the transparency (alpha) for the second rectangle is 0.5. That allows us to see some color come through from the layer below it.

I puzzled over the fact that the right side of the figure isn't blue but more like purple. I finally realized that what's happening is the transparency works here too (and for some reason the background is white). The implication is that the order matters, if we draw the red rectangle last (with alpha = 0.5) the result is different, as shown.

An excellent beginner's tutorial for Quartz drawing is here.

// clang prog.m -o prog -framework Cocoa -fobjc-gc-only
#import <Cocoa/Cocoa.h>

int main(int argc, char * argv[]) {
    // method 1
    CFStringRef cfstr = CFSTR("x.pdf");
    CFURLRef urlref = CFURLCreateWithFileSystemPath (
    if (urlref == NULL) { 
        NSLog(@"some problem with the url");
        return 0; 
    // method 2
    NSString *s = [NSString stringWithUTF8String:"x.pdf"];
    NSURL *url = [NSURL fileURLWithPath:s];
    urlref = (CFURLRef)url;
    CGContextRef myPDFContext = NULL;
    myPDFContext = CGPDFContextCreateWithURL (
    CGRect rect = CGRectMake (0, 0, 500, 250);              
    CGContextBeginPage(myPDFContext, &rect);

    CGRect r1, r2, r3, r4;
    r1 = CGRectMake (21,21,100,200);
    r2 = CGRectMake (21,21,200,100);
    CGContextSetRGBFillColor (myPDFContext,1,0,0,1);
    CGContextFillRect (myPDFContext, r1);

    CGContextSetRGBFillColor (myPDFContext,0,0,1,0.5);
    CGContextFillRect (myPDFContext, r2);

    r3 = CGRectMake (221,21,100,200);
    r4 = CGRectMake (221,21,200,100);

    CGContextSetRGBFillColor (myPDFContext,0,0,1,1);
    CGContextFillRect (myPDFContext,r4);

    CGContextSetRGBFillColor (myPDFContext,1,0,0,0.5);
    CGContextFillRect (myPDFContext, r3);

    return 0;