4

I have a graphics-related Document-based Mac App. My app's Documents may have multiple "pages". Each page has an NSView "canvas" object.

My app has several export options which are implemented as methods which return an NSData object (which is then written to disk).

I would like to implement a PDF export option in a method which:

  1. Creates an in-memory PDF
  2. loops thru my document's canvas views and renders each in a new page in the in-memory PDF
  3. returns the in-memory, multi-page PDF as an NSData

The code below is what I am currently trying.

Each page in my document is 800 x 600 pixels.

When I write the resulting NSData object to disk, the write operation succeeds, but the file on disk is corrupted somehow. The file cannot be opened in Preview or any other app I've tried.

What am I doing wrong?

NSMutableData *data = [NSMutableData data];
CGDataConsumerRef consumer = CGDataConsumerCreateWithCFData((CFMutableDataRef)data);

CGRect mediaBox = CGRectMake(0.0, 0.0, 800.0, 600.0);
CGContextRef ctx = CGPDFContextCreate(consumer, &mediaBox, NULL);
CFRelease(consumer);

NSGraphicsContext *gc = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:NO];

for (NSView *canvas in myCanvases) {
    CGContextBeginPage(ctx, &mediaBox);
    CGContextSaveGState(ctx);

    [canvas displayRectIgnoringOpacity:mediaBox inContext:gc];

    CGContextRestoreGState(ctx);
    CGContextEndPage(ctx);
}

CGPDFContextClose(ctx); // UPDATE: this line was originally missing. see answer below
CGContextRelease(ctx);

...

NSError *err = nil;
if (![data writeToFile:s options:NSDataWritingAtomic error:&err]) {
    if (err) {
        NSLog(@"%@", err);
    }
}
Todd Ditchendorf
  • 11,217
  • 14
  • 69
  • 123

2 Answers2

5

OP here. I have solved the problem. I was missing a final call to CGPDFContextClose().

So before releasing the context...

CGPDFContextClose(ctx);
CGContextRelease(ctx);
Todd Ditchendorf
  • 11,217
  • 14
  • 69
  • 123
  • 1
    I can't believe it was that simple, thanks! I wasted hours on this, as the bug was only happening when I started mixing NSString drawing (via AppKit extensions) on OS 10.10, but not on OS 10.9, so I was looking for something much more complicated. And it had worked before without closing the context, for months! – charles Apr 25 '15 at 15:39
0

I found this a most useful answer (and got to upvote the OP on both the question and the answer!). There were a few hiccups translating it into Swift 3, but this works like a charm, and it will work on iOS...

let data = NSMutableData()
guard let consumer = CGDataConsumer(data: data) else { Swift.print("Aargh - no CGDataConsumer"); return }
var mediaBox = CGRect(x: 0, y: 0, width: 800, height: 600)
guard let ctx = CGContext(consumer: consumer, mediaBox: &mediaBox, nil) else { Swift.print("Aargh - no CGContext"); return }

//let gc = NSGraphicsContext(cgContext: ctx, flipped: false) 
//I don't need the NS context, as my drawing routines just use CGContext

for page in self.pages { // Array of page models
    ctx.beginPage(mediaBox: &mediaBox)
    ctx.saveGState()
    myDraw(page, context: ctx, options: []) // My routine
    ctx.restoreGState()
    ctx.endPage()
}
ctx.closePDF()
do {
    try data.write(toFile: "/Users/grimxn/Test.pdf", options: .atomic)
} catch {
    Swift.print("Aargh - failed to write output file")
}
Grimxn
  • 22,115
  • 10
  • 72
  • 85