Printing PDF on iOS, background image not printing - ios

I'm attempting to print a PDF file in my Cordova application on iOS.
The file is generated using jsPDF in the Cordova app and then I've modified the katzer cordova-plugin-printer to accept the raw PDF data as a string, convert it to NSData and print it out.
- (void) printPDFFromData:(CDVInvokedUrlCommand*)command
{
if (!self.isPrintingAvailable)
{
return;
}
NSArray* arguments = [command arguments];
NSString* documentData = [arguments objectAtIndex:0];
NSData* pdfData = [documentData dataUsingEncoding:NSUTF8StringEncoding];
UIPrintInteractionController* controller = printController;
[self adjustSettingsForPrintController:controller];
controller.printingItem = pdfData;
[self openPrintController:controller];
[self commandDelegate];
}
Using the iOS print simulator (I don't have access to an AirPrint printer), the PDF appears to print out, except that the background image is not printed, just the vector drawings overlaying it.
The same raw output data when saved to a PDF file will display the background image and when you print that file, the background image is printed.
Is this just an anomaly of the printer simulator or do I need to somehow set the print controller to be able to print the image in the document?

I found a solution to the issue. Something was getting lost in the decoding of the string data from JavaScript into Objective-C.
To get around this I Base64 encoded the PDF document in my JS side before sending it off to the plugin:
var startIndexOfBase64Data = 28;
var base64Document = doc.output('dataurlstring').substring(startIndexOfBase64Data);
window.plugin.printer.printPDFFromData(base64Document);
Then I needed to add
NSData+Base64.m and NSData+Base64.h
from this sample project into my plugins directory to allow this line of code to convert the Base64 string into NSData:
NSData* pdfData = [NSData dataFromBase64String:documentData];
Then the document then printed out untainted.
Now I'm off to see if I can get it working with Android.

Related

How can I write XMP metadata to a QuickTime video on iOS?

I’m trying to attach some XMP metadata to a QuickTime video I'm exporting using AVAssetExportSession.
AVFoundation does support writing metadata (AVMetadataItem) and I’ve managed to export simple values which can subsequently be examined using exiftool:
AVMutableMetadataItem *item = [AVMutableMetadataItem metadataItem];
item.identifier = [AVMetadataItem identifierForKey:AVMetadataCommonKeyTitle keySpace:AVMetadataKeySpaceCommon];
item.value = #"My Title";
exportSession.metadata = #[item];
But I’m having trouble configuring my AVMetadataItem’s to correctly encode XMP. According to the Adobe XMP spec, XMP in QuickTime videos should be under the moov / udta / XMP_ atoms but I can’t see a way to make hierarchical metadata using the AVFoundation API, or any key space that corresponds to this part of the metadata.
I also need to write XMP metadata to images, and Image I/O does have direct support for this (CGImageMetadataCreateFromXMPData), but I can't find anything equivalent in AVFoundation.
If it's not possible using AVFoundation (or similar), I'll probably look at integrating XMP-Toolkit-SDK but this feels like a clunky solution when AVFoundation almost seems to do what I need.
I finally managed to figure this after trying lots of variations of keys/key spaces and other attributes of AVMetadataItem:
Use a custom XMP_ key in the AVMetadataKeySpaceQuickTimeUserData key space
Set the value not as an NSString but as an NSData containing UTF-8 data for the payload
Set the dataType to raw data
This results in XMP attributes that can be read by exiftool as expected.
NSString *payload =
#"<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"MyAppXMPLibrary\">"
"<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">"
"<rdf:Description rdf:about=\"\" xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\">"
"<xmp:CreatorTool>My App</xmp:CreatorTool>"
"</rdf:Description>"
"</rdf:RDF>"
"</x:xmpmeta>";
NSData *data = [payload dataUsingEncoding:kCFStringEncodingUTF8];
AVMutableMetadataItem *item = [AVMutableMetadataItem metadataItem];
item.identifier = [AVMetadataItem identifierForKey:#"XMP_"
keySpace:AVMetadataKeySpaceQuickTimeUserData];
item.dataType = (NSString *)kCMMetadataBaseDataType_RawData;
item.value = data;
exportSession.metadata = #[item];

Copy GIF to UIPasteboard

I'm trying to copy a GIF image to the UIPasteboard in swift, at the moment it only copies the static version of the image and seems to convert it to PNG looking at the file extension when I upload it somewhere.
Wondered if anyone had any idea how to achieve this? All other soltions I've found only seem to work when getting NSData from a URL rather than from an image in the bundle
For anyone who ever encounters this problem I managed to find a solution
let url: NSURL = NSBundle.mainBundle().URLForResource("\(self.imageNames[indexPath.row])", withExtension: ".gif")!
let data: NSData = NSData(contentsOfURL: url)!
UIPasteboard.generalPasteboard().setData(data, forPasteboardType: "com.compuserve.gif")
As it turns out you do need to use a URL and extract the NSData of the GIF from that URL.
Here I am getting the URL of the GIF that is in my bundle, searching for it using the name and extension of the image. I am then setting the data in the pasteboard and bingo we have an animated GIF when pasting the result from the pasteboard
It doesn't look like the image property on the pasteboard supports the GIF type.
The associated array of representation types is UIPasteboardTypeListImage, which includes types kUTTypePNG and kUTTypeJPEG.
You could probably do this using the NSData from the GIF though:
import MobileCoreServices
// ...
var image = UIImage(...)
let data = NSData(bytes: &image, length: sizeof(UIImage))
UIPasteboard.generalPasteboard().setData(data, forPasteboardType: kUTTypeGIF as String)) // com.compuserve.gif

UIImage AsPNG and AsJPEG fails

I'm using MonoTouch and I have a UIImage (displayed in a UIImageView and it looks good) and I'm trying to convert it to NSData, but AsJPEG and AsPNG returns null. What can be the problem?
My code looks like this:
NSError err;
NSData imageData = CroppedImageView.Image.AsJPEG(); // imageData is null!
if (!imageData.Save ("tmp.png", true, out err)) {
Console.WriteLine("Saving of file failed: " + err.Description);
}
The AsJPEG method calls UIImageJPEGRepresentation and its return value is documented as:
A data object containing the JPEG data, or nil if there was a problem generating the data. This function may return nil if the image has no data or if the underlying CGImageRef contains data in an unsupported bitmap format.
The is similar to many API in iOS (and OSX) where exception are not commonly used (and null is used to report some kind of error).
Anyway you should check your image dimensions and properties - they might give you an hint at something that would not translate into a JPEG bitmap.
Also since the NSData can represent a very large amount of memory you should try to limit it's life, e.g.:
using (NSData imageData = CroppedImageView.Image.AsJPEG ()) {
NSError err;
if (!imageData.Save ("tmp.jpg", true, out err)) {
Console.WriteLine("Saving of file failed: " + err.Description);
}
}
It looks like you are writing to a file in the current directory of the app, this is readonly.
You should use:
var path = System.IO.Path.GetTempFilename();
or
var path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "tmp.png");
Like you would do on other platforms, and use a file from there.
You can also use Environment.SpecialFolder.MyDocuments.
The AsJPEG returned null because the image size was too big (it was taken with an iPhone 5). After I Scaled it down by 2, it generates the data properly.

CGImageSourceCreateImageAtIndex returns nil

I'm trying to convert a pdf file to an image with this solution :
CFURLRef urlRef = (CFURLRef)[NSURL fileURLWithPath:#"http://uat.okitoo.fr/document/document/s595_506cb1811852f/o_specimen-page-seule.pdf"];
CGImageSourceRef src = CGImageSourceCreateWithURL(urlRef, NULL);
NSDictionary* options = [NSDictionary dictionaryWithObject:(id)[NSNumber numberWithInt:500] forKey:(id)kCGImageSourceThumbnailMaxPixelSize];
CGImageRef firstPage = CGImageSourceCreateImageAtIndex(src, 0, (CFDictionaryRef)options);
UIImage* imagePDF = [UIImage imageWithCGImage:firstPage];
CGImageRelease(firstPage);
My pdf file is ok but when I try this code, the image doesn't appear and I have this message : ": ImageIO: CGImageSourceCreateImageAtIndex image source parameter is nil". I can't understand..
I tried with other links but I have the same problem..
Any ideas ?
Is there an other way to convert my pdf file to an image ?
Thanks a lot !
You are using the incorrect API to create the URL, you need URLWithString:
CFURLRef urlRef = (CFURLRef)[NSURL URLWithString:#"http://uat.okitoo.fr/document/document/s595_506cb1811852f/o_specimen-page-seule.pdf"];
The API you are currently using is for urls to files on your local file system.
As the message says, your urlRef is probably nil.
Probably because you didn't mention the real full path but only the file name, and it can't find the file.
If your PDF is a file inside your application bundle, use [[NSBundle mainBundle] pathForResource:#"test" ofType:#"pdf"]. If you downloaded it at runtime to some place in your sandbox, simply use the path to where the PDF has been downloaded.

is it possible to access pdf outline when developing for iOS

being that PDFKit is not available on iOS, how is it possible to get the outline of a pdf document in that environment? Is commercial libraries like FastPdfKit or PSPDFKit the only solution?
It's not TOO tricky to access the pdf outline. My outline parser has about 420 LOC. I'll post some snippets, so you'll get the idea. I can't post the full code as it's a commercial library.
You basically start like this:
CGPDFDictionaryRef outlineRef;
if(CGPDFDictionaryGetDictionary(pdfDocDictionary, "Outlines", &outlineRef)) {
going down to
NSArray *outlineElements = nil;
CGPDFDictionaryRef firstEntry;
if (CGPDFDictionaryGetDictionary(outlineRef, "First", &firstEntry)) {
NSMutableArray *pageCache = [NSMutableArray arrayWithCapacity:CGPDFDocumentGetNumberOfPages(documentRef)];
outlineElements = [self parseOutlineElements:firstEntry level:0 error:&error documentRef:documentRef cache:pageCache];
}else {
PSPDFLogWarning(#"Error while parsing outline. First entry not found!");
}
you parse single items like this:
// parse title
NSString *outlineTitle = stringFromCGPDFDictionary(outlineElementRef, #"Title");
PSPDFLogVerbose(#"outline title: %#", outlineTitle);
if (!outlineTitle) {
if (error_) {
*error_ = [NSError errorWithDomain:kPSPDFOutlineParserErrorDomain code:1 userInfo:nil];
}
return nil;
}
NSString *namedDestination = nil;
CGPDFObjectRef destinationRef;
if (CGPDFDictionaryGetObject(outlineElementRef, "Dest", &destinationRef)) {
CGPDFObjectType destinationType = CGPDFObjectGetType(destinationRef);
The most annoying thing is that you have Named Destinations in most pdf documents, which need additional steps to resolve. I save those in an array and resolve them later.
It took quite a while to "get it right" as there are LOTS of differences in the PDFs that are around, and even if you implement everything in compliance to the PDF reference, some files won't work until you apply further tweaking. (PDF is a mess!)
It is now possible in iOS 11+.
https://developer.apple.com/documentation/pdfkit
You can get the PDFOutline of a PDFDocument.
The PDFOutline's outlineRoot will return outline items if there are any and NULL if none.

Resources