PDF Packages in iOS - objective-c

I've been trying for a while to be able to extract the pdf documents contained in a PDF package with no success. I've found no documentation or example code anywhere, but I know it's not impossible because the Adobe Reader app and the PDFExpert app support it. It is possible that they have their own parser, I hope it doesn't come to that...
Any hint that will point me in the right direction will be greatly appreciated
Edit: after a long time I went back to working on this and finally figured it out.
Special thanks to iPDFDev for pointing me in the right direction!!
Here's the code on how to obtain each inner CGPDFDocumentRef:
NSURL *url = [NSURL fileURLWithPath:filePath isDirectory:NO];
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((__bridge CFURLRef)url);
CGPDFDictionaryRef catalog = CGPDFDocumentGetCatalog(pdf);
CGPDFDictionaryRef names = NULL;
if (CGPDFDictionaryGetDictionary(catalog, "Names", &names)) {
CGPDFDictionaryRef embFiles = NULL;
if (CGPDFDictionaryGetDictionary(names, "EmbeddedFiles", &embFiles)) {
// At this point you know this is a Package/Portfolio
CGPDFArrayRef nameArray = NULL;
CGPDFDictionaryGetArray(embFiles, "Names", &nameArray);
// nameArray contains the inner documents
// it brings the name and then a dictionary from where you can extract the pdf
for (int i = 0; i < CGPDFArrayGetCount(nameArray); i+=2) {
CGPDFStringRef name = NULL;
CGPDFDictionaryRef dict = NULL;
if (CGPDFArrayGetString(nameArray, i, &name) &&
CGPDFArrayGetDictionary(nameArray, i+1, &dict)) {
NSString *_name = [self convertPDFString:name];
CGPDFDictionaryRef EF;
if (CGPDFDictionaryGetDictionary(dict, "EF", &EF)) {
CGPDFStreamRef F;
if (CGPDFDictionaryGetStream(EF, "F", &F)) {
CFDataRef data = CGPDFStreamCopyData(F, NULL);
CGDataProviderRef provider = CGDataProviderCreateWithCFData(data);
CGPDFDocumentRef _doc = CGPDFDocumentCreateWithProvider(provider);
if (_doc) {
// save the docRef somewhere (_doc)
// save the pdf name somewhere (_name)
}
CFRelease(data);
CGDataProviderRelease(provider);
}
}
}
}
}
}
- (NSString *)convertPDFString:(CGPDFStringRef)string {
CFStringRef cfString = CGPDFStringCopyTextString(string);
NSString *result = [[NSString alloc] initWithString:(__bridge NSString *)cfString];
CFRelease(cfString);
return result;
}

By PDF packages I assume you refer to PDF portfolios. The files in a PDF portfolio are basically document attachments with some extended attributes and they are located in the EmbeddedFiles tree. You start with the document catalog dictionary. From the document catalog dictionary you retrieve the /Names dictionary. From the /Names dictionary, if exists (it is optional), you retrieve the /EmbeddedFiles dictionary. If it exists, it represents the head of the embedded files tree (a name tree in the PDF specification). The PDF specification (available here: http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf) describes in section 7.9.6 the name trees and you'll get the idea how to parse the tree.
The tree maps string identifiers to file specification dictionaries (section 7.11.3). From the file specification dictionary you retrieve the value of the /EF key which is the embedded file stream (section 7.11.4). The stream associated with this object is the file content you're looking for.

Related

How to combine two pdfs without losing any information?

My goal is to combine two PDFs. One has 10 pages, and another has 6 pages, so the output should be 16 pages. My approach is to load both PDFs into two NSData stored in an NSMutableArray.
Here is my saving method:
NSMutableData *toSave = [NSMutableData data];
for(NSData *pdf in PDFArray){
[toSave appendData:pdf];
}
[toSave writeToFile:path atomically:YES];
However the output PDF only has the second part, which only contains 6 pages. So I don't know what did I miss. Can anyone give me some hints?
PDF is a file format which describes a single document. You cannot concatenate to PDF files to get the concatenated document.
But might achieve this with PDFKit:
Create both documents with initWithData:.
Insert all pages of the second document into the first one with insertPage:atIndex:.
This should look like:
PDFDocument *theDocument = [[PDFDocument alloc] initWithData:PDFArray[0]]
PDFDocument *theSecondDocument = [[PDFDocument alloc] initWithData:PDFArray[1]]
NSInteger theCount = theDocument.pageCount;
NSInteger theSecondCount = theSecondDocument.pageCount;
for(NSInteger i = 0; i < theSecondCount; ++i) {
PDFPage *thePage = [theSecondDocument pageAtIndex:i];
[theDocument insertPage:thePage atIndex:theCount + i];
}
[theDocument writeToURL:theTargetURL];
You have to add either #import <PDFKit/PDFKit.h> or #import PDFKit; to your source file, and you should add PDFKit.framework to the Linked Frameworks and Libraries of the build target in Xcode.
I've made a Swift command line tool to combine any number of PDF files. It takes the output path as the first argument and the input PDF files as the other arguments. There's no error handling whatsoever, so you can add that if you want to. Here's the full code:
import PDFKit
let args = CommandLine.arguments.map { URL(fileURLWithPath: $0) }
let doc = PDFDocument(url: args[2])!
for i in 3..<args.count {
let docAdd = PDFDocument(url: args[i])!
for i in 0..<docAdd.pageCount {
let page = docAdd.page(at: i)!
doc.insert(page, at: doc.pageCount)
}
}
doc.write(to: args[1])

PHPhotoLibrary getting album and photo info

I am trying to get info on all the albums/photos using the PHPhotoLibrary. I barely know objective C, and i've looked at some tutorial/sample but couldn't find everything that I needed.
Here is a link to the sample code I based my code on.
https://developer.apple.com/library/ios/samplecode/UsingPhotosFramework/Introduction/Intro.html#//apple_ref/doc/uid/TP40014575-Intro-DontLinkElementID_2
So far I was able to get the albums name and identifier. And I am getting a list of photos, I am able to get their identifier as well, but not the filename. But if I put a break point in my fonction and look at my PHAsset pointer values, I can see the filename there (inside _filename), but if I try to call the variable with the filename in it, the variable does not exist.
So if anyone can provide a sample code to get all info on albums/photos/thumbnail that would be awesome. Or just getting the filename would be a good help.
Here is the code I have tried so far:
-(void)awakeFromNib{
NSMutableArray *allPhotos = self.getAllPhotos;
for (int x = 0; x < allPhotos.count; x ++)
{
PHAsset *photo = [self getPhotoAtIndex:x];
PHAssetSourceType source = photo.sourceType;
NSString *id = photo.localIdentifier;
NSString *description = photo.description;
NSUInteger height = photo.pixelHeight;
NSUInteger width = photo.pixelWidth;
NSLog(#"Test photo info");
}
}
-(PHAsset*) getPhotoAtIndex:(NSInteger) index
{
return [self.getAllPhotos objectAtIndex:index];
}
-(NSMutableArray *) getAllPhotos
{
NSMutableArray *photos = [[NSMutableArray alloc] init];
PHFetchOptions *allPhotosOptions = [[PHFetchOptions alloc] init];
allPhotosOptions.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:#"creationDate" ascending:YES]];
PHFetchResult *allPhotos = [PHAsset fetchAssetsWithOptions:allPhotosOptions];
PHFetchResult *fetchResult = #[allPhotos][0];
for (int x = 0; x < fetchResult.count; x ++) {
PHAsset *asset = fetchResult[x];
photos[x] = asset;
}
return photos;
}
As you can see, I can get the image height and width, its id, but cannot get the url to it.
I have found a way to get the url of my photo.
-(void)getImageURL:(PHAsset*) asset
{
PHContentEditingInputRequestOptions *options = [[PHContentEditingInputRequestOptions alloc] init];
[options setCanHandleAdjustmentData:^BOOL(PHAdjustmentData *adjustmentData) {
return [adjustmentData.formatIdentifier isEqualToString:AdjustmentFormatIdentifier] && [adjustmentData.formatVersion isEqualToString:#"1.0"];
}];
[asset requestContentEditingInputWithOptions:options completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info)
{
NSURL* url = contentEditingInput.fullSizeImageURL;
}];
}
Filenames in the Photos library are an implementation detail and subject to change. There are various private API for discovering them (or ways to use valueForKey or other public introspection APIs to find where they're hidden), they aren't something to be relied upon. In particular, an asset that's been edited is likely to have a different filename than the original.
What do you need a filename/URL for? If you're just uniquely identifying the asset across launches of your app, use localIdentifier. If you're showing it to the user... why? Something like IMG_0234.jpg vs IMG_5672.jpg has little meaning to the average user.
To fetch the assets in a specific album, use fetchAssetsInAssetCollection:options:. To fetch the album(s) containing a specific asset, use fetchAssetCollectionsContainingAsset:withType:options:. To discover the list(s) of albums, use other APIs on PHAssetCollection and its superclass PHCollection.

Persisting bookmark in core-data

I have an OSX application that is supposed to have a list of files from anywhere in the user's disk.
The first version of the app saves the path to these files in a core-data model.
However, if the file is moved or renamed, the tool loses its purpose and the app can crash.
So I decided to use bookmarks. It seems to be working, but every time I try to recover the data, I get the old path of the files. Why is that? What am I missing?
My core-data entity uses a binary data field to persist the bookmark.
The bookmark itself is done like this:
NSData * bookmark = [filePath bookmarkDataWithOptions:NSURLBookmarkCreationMinimalBookmark
includingResourceValuesForKeys:NULL
relativeToURL:NULL
error:NULL];
And on loading the application, I have a loop to iterate all the table and recover the bookmark like this:
while (object = [rowEnumerator nextObject]) {
NSError * error = noErr;
NSURL * bookmark = [NSURL URLByResolvingBookmarkData:[object fileBookmark]
options:NSURLBookmarkResolutionWithoutUI
relativeToURL:NULL
bookmarkDataIsStale:NO
error:&error];
if (error != noErr)
DDLogCError(#"%#", [error description]);
DDLogCInfo(#"File Path: %#", [bookmark fileReferenceURL]);
}
If I rename the file, the path is null. I see no difference between storing this NSData object and a string with the path. So I am obviously missing something.
Edit:
I also often get an error like this: CFURLSetTemporaryResourcePropertyForKey failed because it was passed this URL which has no scheme.
I appreciate any help, thanks!
I can't find any issues in my code, so I changed it.
After looking for the reason of the "no scheme" message, I came to the conclusion some third-party application is required for this code to work, and that's undesirable.
I am now using aliases. This is how I create them:
FSRef fsFile, fsOriginal;
AliasHandle aliasHandle;
NSString * fileOriginalPath = [[filePath absoluteString] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
OSStatus status = FSPathMakeRef((unsigned char*)[fileOriginalPath cStringUsingEncoding: NSUTF8StringEncoding], &fsOriginal, NULL);
status = FSPathMakeRef((unsigned char*)[fileOriginalPath cStringUsingEncoding: NSUTF8StringEncoding], &fsFile, NULL);
OSErr err = FSNewAlias(&fsOriginal, &fsFile, &aliasHandle);
NSData * aliasData = [NSData dataWithBytes: *aliasHandle length: GetAliasSize(aliasHandle)];
And now I recover the path like this:
while (object = [rowEnumerator nextObject]) {
NSData * aliasData = [object fileBookmark];
NSUInteger aliasLen = [aliasData length];
if (aliasLen > 0) {
FSRef fsFile, fsOriginal;
AliasHandle aliasHandle;
OSErr err = PtrToHand([aliasData bytes], (Handle*)&aliasHandle, aliasLen);
Boolean changed;
err = FSResolveAlias(&fsOriginal, aliasHandle, &fsFile, &changed);
if (err == noErr) {
char pathC[2*1024];
OSStatus status = FSRefMakePath(&fsFile, (UInt8*) &pathC, sizeof(pathC));
NSAssert(status == 0, #"FSRefMakePath failed");
NSLog(#"%#", [NSString stringWithCString: pathC encoding: NSUTF8StringEncoding]);
} else {
NSLog(#"The file disappeared!");
}
} else {
NSLog(#"CardCollectionUserDefault was zero length");
}
}
However, I am still curious on why my previous code failed. I appreciate any thoughts on that. Thanks!

data from txt file to NSArray

What I would like to do is the following:
I'm using XCode 4.6.1, building an iOS App
I have an textfile (.txt) that contains my data. It is fetched from a server.
The data it contains is like this:
username:userid:realname:clienttype:clientversion:latitude:longitude:number:anothernumber:
So data is seperated by ":" and all data is user defined.
Because there are 2 types of clients: clienttype1 and clienttype2
I've devided the .txt file into an array using
NSArray *dataClient = [datafile componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; //to load txt file line for line in NSArray
NSIndexSet *clientindex = [data1 indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
NSRange range = [(NSString *)obj rangeOfString:#":clienttype1:"];
if (range.location != NSNotFound)
{
return YES;
}
return NO; // Set the index
NSArray *firstClients = [dataClient objectsAtIndexes:clientindex]; // create array with clienttype1 only
Now I have an array with only objects of clienttype1 data.
As this:
username:userid:realname:clienttype1:clientversion:latitude:longitude:number:anothernumber:
username:userid:realname:clienttype1:clientversion:latitude:longitude:number:anothernumber:
How can I separate the data per user (so per line, cause each line is a different user). So that I can use username, userid etc.
As an example plot location by lat. and lon. on a map with username as title. I know how to plot, make annotations etc. It is just how to get to that data.
I was thinking of a way to read the firstClients array line for line to add each type of data into a different array. In the way of: userNameArray, useridArray etc.
In this way I can fetch data per Array. But how to do this.
Any help is welcome!
Ok found my solution. If anything better pops up please advise.
Used this:
for (int i = 0; i < [firstClients count]; i++) {
NSString *oneLine = [atcClients objectAtIndex:i];
NSArray *oneLineSeparated = [oneLine componentsSeparatedByString:#":"];
}
Now I got an Array with: username, userid, realname etc.

Seek to file position in Objective-C

What it's the equivalent in Objective-C of this C code:
FILE* file = fopen( [filePath UTF8String], "r, ccs=UTF-8");
if (file != 0)
{
char buffer[1024];
//seek to file position....
fseek(file,11093, SEEK_CUR);
int cnt = 0;
while(fgets(buffer, 1024, file) != NULL)
{
if (cnt>0) {
if(buffer[0] == 'a') {
break;
}
//Objective c syntax....
NSString *string = [[NSString alloc] initWithCString: buffer];
}
cnt++;
}
fclose(file);
}
That is the equivalent. Objective-C is built on top of C, so every C function is usable in Objective-C.
There is a class hierarchy rooted at NSStream which, at first glance, may appear to be the Objective-C version of file streams--and for many uses, it is. But if you need to seek through an arbitrary stream, you'll want to keep using fopen(), fseek(), etc.
An instance of NSInputStream created from a path to a file on disk will be seekable by getting/setting its NSStreamFileCurrentOffsetKey property. However, it's often awkward to adapt existing FILE *-based code.
I guess what I'm saying is that if fopen() works for you, there's no need to stop using it. :)