views:

275

answers:

2

A little bit of background: I'm appending an image to an already existing pdf file. I found out that there isn't a way to just add a page to it, you must recreate the whole pdf, then remove the old one then move the new one. (If that is wrong please tell me, and it will save me a lot of headaches.) I've accomplished that part, but I'm now trying to copy of the Auxiliary Info (Title, Author, Keys, etc). Problem is there are so many type problems that the current method I'm using is to get the CGPDFDocumentRef's CGPDFDictionaryRef, then call CGPDFDictionaryApplierFunction. In the C function I pass it, I am extracting each key to an NSMutableDictionary so that I can then do something with the values and not have them locked in this terrible CGPDF format.

So basically my question would be: Is there a better way of doing this? I've stared at so many documentation files I couldn't imagine missing anything, but really hope I have because the amount of workaround I'm having to do is getting absurd.

A: 

You might accomplish what you are describing by drawing into CGPDFContext, but if you have a complex PDF, you may be attempting something beyond what the API was intended for. You might take a look at section 3.4 of the PDF spec and see what you're getting into.

jbm
Which PDF spec are you referring you? Supplement ISO 32000, Supplement ISO 32000-1, or the Doc Management? None of those seem really helpful. As for the CGPDFContext, as far as I can tell, thats for drawing, which is what I use. But to copy a PDF, I have to read from one using a CGPDFDocumentRef then draw to the CGPDFContext, according to Apples Quartz 2D Programming Guide. (http://developer.apple.com/mac/library/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_pdf/dq_pdf.html)
alunsford3
The plain "spec" will suffice, as the section I referenced hasn't changed much recently, as far as rewriting the xref, etc.:http://www.adobe.com/devnet/acrobat/pdfs/pdf_reference_1-7.pdf
jbm
Ah thanks. I think thats a bit more low level than what I'm looking for. I'll mention it to my employer and leave it up to them. Haha.
alunsford3
+1  A: 

I've completed my workaround. I would still like to know if there is any better way to do this, so if you know of one or have any suggestions I'm willing to try them out.

Notes: I only check for bools, ints, strings, and arrays because according to Apple's documentation thats all that should be in the Auxiliary Information in a PDF. The array bit is untested, because I dont have a PDF with an array in the Aux Info. If someone would like to test that for me or link to a pdf with one in it I will gladly test it out.

First, in the class header create an id selfClass outside the @interface tags so that the C functions called by CGPDFDictionaryApplyFunction can access the current class. Also add an NSDictionary *auxInfo for storing the information. Once extracted, the NS type can be easily cast like this:

CFDictionaryRef newDictionary = (CFDictionaryRef)[self auxInfo];

I was actually done last night but thought I had to do another round of looping to convert from NS to CF, forgetting they were token-free bridged. So there you have it, hope everyone benefits from my labors. Ask questions if you need clarification. And once again, if there is an easier way to do this, if only an optimization of my code, please say so. I know this isn't a very elegant way to do this but it works for now.

- (void)extractPDFDictionary:(CGPDFDocumentRef)pdf{
    NSLog(@"extractingPDFDictionary");
    CGPDFDictionaryRef oldDict = CGPDFDocumentGetInfo(pdf);
    CGPDFDictionaryApplyFunction(oldDict, copyDictionaryValues, NULL);
}

void copyDictionaryValues (const char *key, CGPDFObjectRef object, void *info) {
    NSLog(@"key: %s", key);
    CGPDFObjectType type = CGPDFObjectGetType(object);
    switch (type) {
        case kCGPDFObjectTypeString: {
            CGPDFStringRef objectString;
            if (CGPDFObjectGetValue(object, kCGPDFObjectTypeString, &objectString)) {
                NSString *tempStr = (NSString *)CGPDFStringCopyTextString(objectString);
                [[selfClass auxInfo] setObject:tempStr
                                         forKey:[NSString stringWithCString:key encoding:NSUTF8StringEncoding]];
                [tempStr release];
                NSLog(@"set string value");
            }
        }
        case kCGPDFObjectTypeInteger: {
            CGPDFInteger objectInteger;
            if (CGPDFObjectGetValue(object, kCGPDFObjectTypeInteger, &objectInteger)) {
                [[selfClass auxInfo] setObject:[NSNumber numberWithInt:objectInteger]
                                         forKey:[NSString stringWithCString:key encoding:NSUTF8StringEncoding]];
                NSLog(@"set int value");
            }
        }
        case kCGPDFObjectTypeBoolean: {
            CGPDFBoolean objectBool;
            if (CGPDFObjectGetValue(object, kCGPDFObjectTypeBoolean, &objectBool)) {
                [[selfClass auxInfo] setObject:[NSNumber numberWithBool:objectBool]
                                         forKey:[NSString stringWithCString:key encoding:NSUTF8StringEncoding]];
                NSLog(@"set boolean value");
            }
        }
        case kCGPDFObjectTypeArray : {
            CGPDFArrayRef objectArray;
            if (CGPDFObjectGetValue(object, kCGPDFObjectTypeArray, &objectArray)) {
                NSArray *tempArr = [selfClass copyPDFArray:objectArray];
                [[selfClass auxInfo] setObject:tempArr
                                         forKey:[NSString stringWithCString:key encoding:NSUTF8StringEncoding]];
                [tempArr release];
                NSLog(@"set array value");
            }
        }

    }
}

- (NSArray *)copyPDFArray:(CGPDFArrayRef)arr{
    int i = 0;
    NSMutableArray *temp = [[NSMutableArray alloc] init];
    for(i=0; i<CGPDFArrayGetCount(arr); i++){
        CGPDFObjectRef object;
        CGPDFArrayGetObject(arr, i, &object);
        CGPDFObjectType type = CGPDFObjectGetType(object);
        switch(type){
            case kCGPDFObjectTypeString: {
                CGPDFStringRef objectString;
                if (CGPDFObjectGetValue(object, kCGPDFObjectTypeString, &objectString)) {
                    NSString *tempStr = (NSString *)CGPDFStringCopyTextString(objectString);
                    [temp addObject:tempStr];
                    [tempStr release];
                }
            }
            case kCGPDFObjectTypeInteger: {
                CGPDFInteger objectInteger;
                if (CGPDFObjectGetValue(object, kCGPDFObjectTypeInteger, &objectInteger)) {
                    [temp addObject:[NSNumber numberWithInt:objectInteger]];
                }
            }
            case kCGPDFObjectTypeBoolean: {
                CGPDFBoolean objectBool;
                if (CGPDFObjectGetValue(object, kCGPDFObjectTypeBoolean, &objectBool)) {
                    [temp addObject:[NSNumber numberWithBool:objectBool]];
                }
            }
            case kCGPDFObjectTypeArray : {
                CGPDFArrayRef objectArray;
                if (CGPDFObjectGetValue(object, kCGPDFObjectTypeArray, &objectArray)) {
                    NSArray *tempArr = [selfClass copyPDFArray:objectArray];
                    [temp addObject:tempArr];
                    [tempArr release];
                }
            }
        }
    }
    return temp;
}
alunsford3
+1: wow great work, going to try it out with my modifications (since im not really using it just to parse the aux info.) I will let you know if the array bit works too. Thanks again.
Jesse Naugher
No problem. Just a small update to the code though, I forgot to put `break;` at the end of each case. It still works the same I think, but it makes the switch run properly. I did a little more testing and I think its missing the keywords and Subject keys somehow. I've got to look into it some.
alunsford3
one problem i am running into as i expand your code to include other objects, is there is no guideline onto what a pdf name type is equal to in CGPDF, i try to use a CGPDFStringRef like for the string case, and it just completely goes wacko. ideas?
Jesse Naugher
ah i found it after digging around for a while in the reference: PDF name reference (represented as a constant C string).
Jesse Naugher
What exactly were you trying to access? `kCGPDFContextTitle` is the value that holds the title in the CGPDFDict. All the value types under Auxiliary Dictionary Keys in the CGPDFContext Reference are being copied I think. They only include bools, ints, strings, and arrays, so it should have been copied. I found out that the Subject wasn't copied if it had a space in it. No clue why keywords weren't copied. I had to create a method to pull them from the NSMutDict and set them.
alunsford3
ah im not after the auxiliary dictionary per se. using this to access annotation dictionaries in a page to extract them. :). Also running into issues with infinite loops trying to copy over an object thats a dictionary recursively.. :/
Jesse Naugher
Ah alright. Good luck.
alunsford3