I have a pdf and I'd like to be able to allow users to write on top of it (to sign a signature) in the middle of my iPad app, and then save the pdf with their signature.
What is the best way to do it? I'm sure there exists something already to do this. It seems unnecessary to rewrite the code to make this work.
You can do this by drawing text into the pdf context using core graphics functions. A code snippet to help this:
//Assuming this code inside a context
CGAffineTransform textTransform = CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, 0.0);
CGContextSetTextMatrix(context, textTransform);
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextSelectFont(context,fontName, fontSize, kCGEncodingMacRoman);
CGContextSetTextDrawingMode(context, kCGTextFill);
const char *st = [#"Sample Text" cStringUsingEncoding:NSASCIIStringEncoding];
CGContextShowTextAtPoint(context, 100.0, 100.0, st, [#"Sample Text" length]);
Related
For some reason I'm unable to get NSTextAttachment images to draw when using core text, although the same image would display fine when the NSAttributedString is added to an UILabel.
On iOS this rendering will give empty spaces for the NSTextAttachments, for OS X, a placeholder [OBJECT] square image is rendered for each NSTextAttachment instead. Is there something else that needs to be done in order to render images with CoreText?
The rendering code:
CGFloat contextHeight = CGBitmapContextGetHeight(context);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)_attributedString);
CGPathRef path = CGPathCreateWithRect(CGRectMake(rect.origin.x,
contextHeight - rect.origin.y - rect.size.height,
rect.size.width,
rect.size.height), NULL);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
CFRelease(framesetter);
CGPathRelease(path);
CGContextSaveGState(context);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0.0f, contextHeight);
CGContextScaleCTM(context, 1.0f, -1.0f);
CTFrameDraw(frame, context);
CGContextRestoreGState(context);
CFRelease(frame);
The reason is simply that NSTextAttachment only works for rendering a NSAttributedString into an UIView/NSView. It can't be used to render into a regular CGContext.
There are two possible ways to solve the problem:
Create a UILabel, CATextLayer or similar, and render it into the graphics context.
Use CTRunDelegate to punch spaces in the text, then loop through all the lines to be rendered and draw the images directly into the CGContext manually. The way to do it is detailed here: https://www.raywenderlich.com/4147/core-text-tutorial-for-ios-making-a-magazine-app. Expect a lot of work if you go down this route, but it works.
The IOS CGContext documentation says that various string output functions are now deprecated in favor of core text. The documentation just says "Use Core Text instead."
if I have
NSString *string ;
What would be the simple, currently approved method for drawing that text to the CGContext?
Here is my overridden drawRect: method to render attributed string with all explanation comments inside. By the time this method is called, UIKit has configured the drawing environment appropriately for your view and you can simply call whatever drawing methods and functions you need to render your content.
/*!
* #abstract draw the actual coretext on the context
*
*/
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
[self.backgroundColor setFill];
CGContextFillRect(context, rect);
if (_ctframe != NULL) CFRelease(_ctframe);
if (_framesetter != NULL) CFRelease(_framesetter);
//Creates an immutable framesetter object from an attributed string.
//Use here the attributed string with which to construct the framesetter object.
_framesetter = CTFramesetterCreateWithAttributedString((__bridge
CFAttributedStringRef)self.attributedString);
//Creates a mutable graphics path.
CGMutablePathRef mainPath = CGPathCreateMutable();
if (!_path) {
CGPathAddRect(mainPath, NULL, CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height));
} else {
CGPathAddPath(mainPath, NULL, _path);
}
//This call creates a frame full of glyphs in the shape of the path
//provided by the path parameter. The framesetter continues to fill
//the frame until it either runs out of text or it finds that text
//no longer fits.
CTFrameRef drawFrame = CTFramesetterCreateFrame(_framesetter, CFRangeMake(0, 0),
mainPath, NULL);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// draw text
CTFrameDraw(drawFrame, context);
//clean up
if (drawFrame) CFRelease(drawFrame);
CGPathRelease(mainPath);
}
Within my app, I use drawRect to draw some text within a UIImage. It writes multiple things in multiple places.. Later, I try to erase some of the text by using
CGContextSetBlendMode(context, kCGBlendModeClear);
CGPoint daPoint = CGPointMake(second.x + 20, second.y + 20);
NSDictionary *textAttributes = #{ NSFontAttributeName: [UIFont boldSystemFontOfSize:25.0],
NSForegroundColorAttributeName: [UIColor clearColor] };
[textString drawAtPoint:daPoint withAttributes:textAttributes];
This works almost perfectly, except there is a small thin stroke left over from the text. I use the same code to draw the text, as I do to erase, except when i'm drawing I use kCGBlendModeNormal. How would I get rid of it completely? Can I draw a box and fill it using kCGBlendModeClear? This is what it looks like currently before erasing:
After erasing:
I would get the bounding rect of the text and then call CGContextClearRect and then fill with the background color if you think that "erasing" the text is really necessary. If you simply "redraw" your rect, that might be another way to solve this problem.
Here's how to get that bounding box for clearing:
CGSize textSize = [textString sizeWithAttributes:textAttributes];
CGRect textFrame = CGRectMake(daPoint.x, daPoint.y, textSize.width, textSize.height);
Hope this helps!
I've found some code which gives me a UIImage out of a PDF-File. It works, but I have two questions:
Is there a possibility to achieve a better quality of the UIImage? (See Screenshot)
I only see the first page in my UIImageView. Do I have to embed the file in a UIScrollView to be complete?
Or is it better to render just one page and use buttons to navigate through the pages?
P.S. I know that UIWebView can display PDF-Pages with some functionalities but I need it as a UIImage or at least in a UIView.
Bad quality Image:
Code:
-(UIImage *)image {
UIGraphicsBeginImageContext(CGSizeMake(280, 320));
CGContextRef context = UIGraphicsGetCurrentContext();
CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("ls.pdf"), NULL, NULL);
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
CGContextTranslateCTM(context, 0.0, 320);
CGContextScaleCTM(context, 1.0, -1.0);
CGPDFPageRef page = CGPDFDocumentGetPage(pdf, 4);
CGContextSaveGState(context);
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFCropBox, CGRectMake(0, 0, 280, 320), 0, true);
CGContextConcatCTM(context, pdfTransform);
CGContextDrawPDFPage(context, page);
CGContextRestoreGState(context);
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultingImage;
}
I know i'm a little late here, but i hope i can help someone else looking for an answer.
As to the questions asked:
I'm afraid the only way to achieve a better image quality is to render a bigger image, and letting the UIImageView resize it for you. I don't think you can set the resolution, but using a bigger image may be a good choice. It won't take too long for the page to render, and the image will have a better quality. PDF files are rendered on demand depending on the zoom level, that's why they seem to have "better quality".
As to rendering all the pages, you can get the number of pages in the document calling CGPDFDocumentGetNumberOfPages( pdf ) and using a simple for loop you can concat all the images generated in one single image. For displaying it, use the UIScrollVIew.
In my opinion, this approach is better than the above, but you should try to optimize it, for example rendering always the current, the previous and the next page. For nice scrolling transition effects, why not use a horizontal UIScrollView.
For more generic rendering code, i always do the rotation like this:
int rotation = CGPDFPageGetRotationAngle(page);
CGContextTranslateCTM(context, 0, imageSize.height);//moves up Height
CGContextScaleCTM(context, 1.0, -1.0);//flips horizontally down
CGContextRotateCTM(context, -rotation*M_PI/180);//rotates the pdf
CGRect placement = CGContextGetClipBoundingBox(context);//get the flip's placement
CGContextTranslateCTM(context, placement.origin.x, placement.origin.y);//moves the the correct place
//do all your drawings
CGContextDrawPDFPage(context, page);
//undo the rotations/scaling/translations
CGContextTranslateCTM(context, -placement.origin.x, -placement.origin.y);
CGContextRotateCTM(context, rotation*M_PI/180);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -imageSize.height);
Steipete already mentioned setting the white background:
CGContextSetRGBFillColor(context, 1, 1, 1, 1);
CGContextFillRect(context, CGRectMake(0, 0, imageSize.width, imageSize.height));
So the last thing to keep in mind is when exporting an image, set the quality to the maximum. For example:
UIImageJPEGRepresentation(image, 1);
What are you doing with the CGContextTranslateCTM(context, 0.0, 320); call?
You should extract the proper metrics form the pdf, with code like this:
cropBox = CGPDFPageGetBoxRect(page, kCGPDFCropBox);
rotate = CGPDFPageGetRotationAngle(page);
Also, as you see, the pdf might has rotation info, so you need to use the CGContextTranslateCTM/CGContextRotateCTM/CGContextScaleCTM depending on the angle.
You also might wanna clip any content that is outside of the CropBox area, as pdf has various viewPorts that you usually don't wanna display (e.g. for printers so that seamless printing is possible) -> use CGContextClip.
Next, you're forgetting that the pdf reference defines a white background color. There are a lot of documents out there that don't define any background color at all - you'll get weird results if you don't draw a white background on your own --> CGContextSetRGBFillColor & CGContextFillRect.
We're displaying PDF content using UIWebViews at the moment. Ideally I would like to be able to display thumbnails in the UITableView without loading many different UIWebViews concurrently... they're slow enough loading one document - never mind 10+!
How do I go about doing this?
I've thought about screen capturing a document loaded using UIDocumentInteractionController or UIWebView but this means they'd all have to be thumbnailed before displaying the table.
Apple supply a whole bunch of methods down at the CoreGraphics level for drawing PDF content directly. As far as I'm aware, none of it is neatly packaged up at the UIKit level, so it may not be a good fit for your project at this point, especially if you're not as comfortable down at the C level. However, the relevant function is CGContextDrawPDFPage; per the normal CoreGraphics way of things there are other methods to create a PDF reference from a data source and then to get a page reference from a PDF. You'll then need to deal with scaling and translating to the view you want, and be warned that you'll need to perform a horizontal flip because PDFs (and OS X) use the lower left as the origin whereas iOS uses the top left. Example code, off the top of my head:
UIGraphicsBeginImageContext(thumbnailSize);
CGPDFDocumentRef pdfRef = CGPDFDocumentCreateWithProvider( (CGDataProviderRef)instanceOfNSDataWithPDFInside );
CGPDFPageRef pageRef = CGPDFDocumentGetPage(pdfRef, 1); // get the first page
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// ignore issues of transforming here; depends on exactly what you want and
// involves a whole bunch of normal CoreGraphics stuff that is nothing to do
// with PDFs
CGContextDrawPDFPage(contextRef, pageRef);
UIImage *imageToReturn = UIGraphicsGetImageFromCurrentImageContext();
// clean up
UIGraphicsEndImageContext();
CGPDFDocumentRelease(pdfRef);
return imageToReturn;
At a guess, you'll probably want to use CGPDFPageGetBoxRect(pageRef, kCGPDFCropBox) to get the page's crop box and then work out how to scale/move that to fit your image size.
Probably easier for your purposes is the -renderInContext: method on CALayer (see QA 1703) — the old means of getting a screenshot was UIGetScreenImage, but that was never really official API and was seemingly temporarily allowed only because of the accidental approval of RedLaser. With the code in the QA you can rig yourself up to get a UIImage from any other view without that view having to be on screen. Which possibly resolves some of your issue with screen capturing? Though it means you can support OS 4.x only.
In either case, PDFs just don't draw that quickly. You probably need to populate the table then draw the thumbnails on a background thread, pushing them upwards when available. You can't actually use UIKit objects safely on background threads but all the CoreGraphics stuff is safe.
Here is sample code considering transformation. :)
NSURL* pdfFileUrl = [NSURL fileURLWithPath:finalPath];
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((CFURLRef)pdfFileUrl);
CGPDFPageRef page;
CGRect aRect = CGRectMake(0, 0, 70, 100); // thumbnail size
UIGraphicsBeginImageContext(aRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
UIImage* thumbnailImage;
NSUInteger totalNum = CGPDFDocumentGetNumberOfPages(pdf);
for(int i = 0; i < totalNum; i++ ) {
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0, aRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetGrayFillColor(context, 1.0, 1.0);
CGContextFillRect(context, aRect);
// Grab the first PDF page
page = CGPDFDocumentGetPage(pdf, i + 1);
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFMediaBox, aRect, 0, true);
// And apply the transform.
CGContextConcatCTM(context, pdfTransform);
CGContextDrawPDFPage(context, page);
// Create the new UIImage from the context
thumbnailImage = UIGraphicsGetImageFromCurrentImageContext();
//Use thumbnailImage (e.g. drawing, saving it to a file, etc)
CGContextRestoreGState(context);
}
UIGraphicsEndImageContext();
CGPDFDocumentRelease(pdf);
Here is a swift 3 method for generating a UIImage thumbnail for a pdf page
static func getThumbnailForPDF(_ urlString:String, pageNumber:Int) -> UIImage? {
let bundle = Bundle.main
let path = bundle.path(forResource: urlString, ofType: "pdf")
let pdfURL = URL(fileURLWithPath: path!)
let pdf = CGPDFDocument(pdfURL as CFURL )!
let page = pdf.page(at: pageNumber)!
let rect = CGRect(x: 0, y: 0, width: 100.0, height: 70.0) //Image size here
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()!
context.saveGState()
context.translateBy(x: 0, y: rect.height)
context.scaleBy(x: 1.0, y: -1.0)
context.setFillColor(gray: 1.0, alpha: 1.0)
context.fill(rect)
let pdfTransform = page.getDrawingTransform(CGPDFBox.mediaBox, rect: rect, rotate: 0, preserveAspectRatio: true)
context.concatenate(pdfTransform)
context.drawPDFPage(page)
let thumbImage = UIGraphicsGetImageFromCurrentImageContext()
context.restoreGState()
UIGraphicsEndImageContext()
return thumbImage
}
If you use PDFKit, there is a method for PDFPage called thumbnail. It is available in iOS11 and above and does the job in just a few lines of code. Here is a simple method for generating a thumbnail of a given page in a PDFDocument.
func makeThumbnail(pdfDocument: PDFDocument?, page: Int) -> UIImage? {
return pdfDocument?.page(at: page)?.thumbnail(of: CGSize(width: 40, height: 40), for: .artBox)
}
Unfortunately it misses official documentation (see, https://developer.apple.com/documentation/pdfkit/pdfpage/2869834-thumbnail). However, there are some comments accessible in the code for PDFPage.