I'm working on a project and I need to create and set my own metadata on a PDF (in order to set a GUID directly into the file).
I am currently able to set classic metadata (e.g creator, keywords...etc) but I can't figure out how to add a custom field.
Here's how I set the metadata:
CFMutableDictionaryRef auxInfo = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, NULL, NULL);
CFDictionaryAddValue(auxInfo, kCGPDFContextCreator, CFSTR("John doo"));
CFDictionaryAddValue(auxInfo, kCGPDFContextAuthor, CFSTR("foo bar"));
CFDictionaryRef auxillaryInformation = CFDictionaryCreateCopy(kCFAllocatorDefault, auxInfo);
CFRelease(auxInfo);
// create a context to draw into
CGContextRef graphicContext = CGPDFContextCreate(PDFDataConsumer, &mediaRect, auxillaryInformation);
CFRelease(auxillaryInformation);
CGDataConsumerRelease(PDFDataConsumer);
I tried to replace the kCGPDFContextThing by a custom name but then when I read the metadata, it doesn't appear at all.
For reading the meta I used that
CGPDFDictionaryRef dict = CGPDFDocumentGetInfo(*pdfDoc);
CGPDFDictionaryGetString(dict, "Creator", &objectValue);
I also try that to add a meta :
NSString* str= #"Hello World";
NSData* data=[str dataUsingEncoding:NSUTF8StringEncoding];
CFDataRef cfdata = CFDataCreate(NULL, [data bytes], [data length]);
CGPDFContextAddDocumentMetadata(graphicContext, cfdata);
But it doesn't seems to work neither, and I'm not sure I understand correctly what's done there.
I am also using PDFNet SDK to help the editing, but it doesn't seems to provide any help about metadata so I'm using quartz.
Any help or advice or anything would be welcome, quite lost and I am not an iOS expert at all !!!
You can do low-level editing of a PDF in PDFNet. Briefly:
Create a new custom entry (on the document's root):
Obj* cust_dict = [[myPDFDoc GetRoot] PutDict:#"_MyCustomData" ];
[cust_dict PutText:#"_myGUID" value:#"123-4567-890"];
Read the custom entry:
Obj* dict = [[myPDFDoc GetRoot] FindObj:#"_MyCustomData"];
DictIterator* itr = [dict Get:#"_myGUID"];
Obj* strObj = [itr Value];
NSString* str = [strObj GetAsPDFText];
NSLog(#"guid is %#", str);
Delete a custom entry:
[[myPDFDoc GetRoot] EraseDictElementWithKey:#"_MyCustomData"];
You could also place metadata on a page's root, or in a more standard location, as outlined here: https://groups.google.com/d/msg/pdfnet-sdk/gtPjLZVbRSQ/Tv5DTb9pRXkJ
For more specific information about adding XMP metadata (as your tag implies you're interested in), try searching XMP on the PDFNet support forum: https://groups.google.com/forum/#!searchin/pdfnet-sdk/XMP
I'm not sure how you would do this with Quartz.
Disclosure: I work for PDFTron, maker of PDFNet.
Related
I am using OCMock v3 do unit testing, I want to test a very simple function named processInfo: , its implementation is showing below:
#implementation MyService
-(void) processInfo{
// get info file path
NSString *infoFilePath = [self getInfoFile];
// read info data from infoFile
NSData *infoData = [[NSData alloc] initWithContentsOfFile:infoFilePath];
// call another function to handle info data
[self handleData:infoData];
}
-(void) handleData:(NSData*) infoData {
...
}
#end
As you see, the processInfo: function gets info file path & read data out then call handleData:(NSData*) function. Pretty simple logic.
I tried to test the above simple function in following way:
-(void) testProcessInfo{
// create dummy info string
NSString* dummyInfoStr = #"dummy info";
// convert above NSString to NSData object
NSData* dummyInfoData = [dummyInfoStr dataUsingEncoding:NSUTF8StringEncoding];
// get the same info file path
NSString* infoFilePath=[self getInfoFile];
// write dummy info data to info file
[data writeToFile:path options:NSDataWritingAtomic error:nil];
// CALL function under test
[myServicePartialMock processInfo];
// I want to verify that handleData:(NSData*) has been invoked with a NSData argument which contains dummy string #"dummy info"
// BUT it failed, even though the real implementation does it.
// For some reason the dummyInfoData is not considered equal to the NSData used in real implementation, though they both contain string #"dummy info"
OCMVerify([myServicePartialMock handleData:dummyInfoData]);
}
I want to verify that function handleData:(NSData*) is called with a NSData argument which contains dummy string #"dummy info", but it failed, even though the real implementation did invoke handleData:(NSData*) with a NSData object read from file which does contain NSString of #"dummy info".
I mean looks like OCMVerify() just simply can not verify it, is it because the dummyInfoData is not read from file?
How can I test the handleData:(NSData*) is called with a NSData type argument that contains dummy string #"dummy info" then?
NSData is designed to encapsulate data in a large variety of formats from a variety of sources, so two NSData objects which have identical behaviour are not likely to be actually identical. In this case, the test instance is probably retaining a copy of the NSString, and the implementation instance is probably retaining a file handle, at least until it's used.
In this case, you probably want to use checkWithBlock: on OCMArg and once there you can check the class and content. You should compare strings rather than their NSData representations, so compare dummyInfoStr and [[NSString alloc] initWithData: infoData, encoding: NSUTF8StringEncoding].
I have added export UTI in shareDocument Application
Another application which will import excel file
Now while sharing from shareDocument Application i don't see my application name in the list. So whats wrong here if somebody can share.
Firstly need to add Document Type. Here its for xls and xlsx format document
Now you need specify Import UTI or Export UTI
For more reference check How do I get my application to show up in the “Open in...” menu on iOS for a specific document type?
For All System-DeclaredUniformTypeIdentifiers
Note : To get exact UTI for specific file format :
NSArray *extensionArray = [NSArray arrayWithObjects:#"doc", #"docx", #"ppt", #"pptx", #"xls", #"xlsx",#"mp3",#"mp4",#"rft",#"rtf",#"pages",#"key",#"numbers",nil];
for (int i=0; i<[extensionArray count]; i++) {
NSString *fileExtension = [extensionArray objectAtIndex:i];
NSString *utiString = (__bridge NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,(__bridge CFStringRef)fileExtension,NULL);
NSLog(#"Extension: %# UTI:%#",fileExtension,utiString);
}
As far as we know that in English speaking country, people always write their names down as format "FirstName MiddleName LastName", such as "Steven Paul Jobs". However in some other language, it is different, for example, in China, people usually write name as "LastName FirstName" format. I want to find out something in "NSLocale Class Reference" but it did not help me a lot.
I know that I can do "switch case" by NSLocaleCountryCode, but I do not know every country's habit.
So, is there any better way to solve the name order issues? Thanks for help.
This functionality has been added in Foundation.framework. Compatible with iOS9+.
I do not see it in the documentation yet but it is in the header files and you can use it in a Swift Playground.
The details of this problem are covered in WWDC 2015 Session 227 (about 22 minutes in). In this session they talk about an NSPersonNameComponents and NSPersonNameComponentsFormatter.
Example:
let components = NSPersonNameComponents()
components.namePrefix = "Mrs."
components.givenName = "Grace"
components.middleName = "Murray"
components.familyName = "Hopper"
NSPersonNameComponentsFormatter.localizedStringFromPersonNameComponents(
components, style: .Default, options: [])
Yes, there's a kit for that written by Mattt who developed Afnetworking:
TTTNameFormatter
TTTNameFormatter formats names according to the internationalization
standards of the AddressBook framework, which determine, for example,
the display order of names and whether or not to delimit components
with whitespace.
TTTNameFormatter is not available on OS X.
Example Usage
TTTNameFormatter *nameFormatter = [[TTTNameFormatter alloc] init];
NSString *frenchName = [nameFormatter stringFromPrefix:nil firstName:#"Guillaume" middleName:#"François" lastName:#"Antoine" suffix:#"Marquis de l'Hôpital"];
NSLog(#"%#", frenchName);
// "Guillaume François Antoine Marquis de l'Hôpital"
NSString *japaneseName = [nameFormatter stringFromFirstName:#"孝和" lastName:#"関"];
NSLog(#"%#", japaneseName);
// "関孝和"
get it here:
https://github.com/mattt/FormatterKit
You can use the AddressBook framework to get the name order. Just add it to the project and do #import "AddressBookUI/AddressBookUI.h", and then you can use this method, for example:
- (NSString*)localNameFormatterWithFirstName:(NSString*)firstName middleName:(NSString*)middleName andLastName:(NSString*)lastName {
ABRecordRef record = ABPersonCreate();
ABRecordSetValue(record, kABPersonFirstNameProperty, (__bridge CFStringRef)firstName, NULL);
ABRecordSetValue(record, kABPersonMiddleNameProperty, (__bridge CFStringRef)middleName, NULL);
ABRecordSetValue(record, kABPersonLastNameProperty, (__bridge CFStringRef)lastName, NULL);
NSString *displayName = (__bridge_transfer NSString*)ABRecordCopyCompositeName(record);
CFRelease(record);
return displayName;
}
Any argument that is nil will be ignored.
I am creating a PDF using UIGraphicsBeginPDFContextToData( pdfData, self.rect, dictionary);
I set dictionary=[[NSDictionary alloc] initWithObjectsAndKeys:masterPassword,kCGPDFContextOwnerPassword,password, kCGPDFContextUserPassword, kCFBooleanFalse, kCGPDFContextAllowsCopying, kCFBooleanTrue, kCGPDFContextAllowsPrinting, nil];
When i give kCGPDFContextUserPassword with more than five characters, the kCGPDFContextOwnerPassword is not working. Please someone help me with this issue.
You can find a pdf example here.
This pdf contains: kCGPDFContextOwnerPassword as "admin", kCGPDFContextUserPassword as "qwerty".
Some background... I am writing code that interacts with javascript via a ObjC-JS bridge utilizing UIWebView's stringByEvaluatingJavaScriptFromString:. The idea is that the "brains" of the app be in JS which tells Objective-C how to behave. There are multiple benefits to this like reduced binary size, flexible updates, etc. However, there is a case where there is some Objective-C only object that the JS needs to have a reference to (JS instructs ObjC when to use/remove the object). This is being done by placing the native object in a dictionary with a unique identifier which can be passed as a string to JS (over the bridge). My problem stems with coming up with a nice identifier for said native Objective-C object.
Thus, I am trying to convert a reference to an object to a string with no luck. This is what I have:
// anObject is a custom class
NSValue *handle = [NSValue valueWithPointer:(__bridge const void *)anObject];
NSData *data = [NSData dataWithValue:handle];
NSString *stringHandle = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
The dataWithValue: function (taken from this SO post):
+ (NSData *)dataWithValue:(NSValue *)value {
NSUInteger size;
const char* encoding = [value objCType];
NSGetSizeAndAlignment(encoding, &size, NULL);
void* ptr = malloc(size);
[value getValue:ptr];
NSData* data = [NSData dataWithBytes:ptr length:size];
free(ptr);
return data;
}
Walking through it in the debugger shows me a nil value for stringHandle:
What am I doing wrong?
What you're doing wrong is trying to treat an address as if it's a UTF-8 encoded string. An address -- or any other chunk of arbitrary data -- isn't very likely to be valid UTF-8 data. (If by chance it were, it still wouldn't be the string you expect.)
If you're trying to get a string containing the pointer value, i.e., the address of the original object, that's just [NSString stringWithFormat:#"%p", anObject];
If you really need to do it from the NSValue, then replace anObject with [theValue pointerValue].
If you want to pretty-print arbitrary data, see How to convert an NSData into an NSString Hex string?
You can get a string representation by calling the NSObject method "description". You can override the "description" method in a subclass if you need.
An NSValue of a pointer will be an object holding the 4 bytes of the 32-bit pointer. It will not hold any of the data pointed to in RAM.