How to parse PDF in Objective C for iPad - objective-c

I am stuck with parsing a PDF file. Please guide me how to do this.
Header file.
//PDFViewer.h
#interface PDFViewer : UIView
{
CGPDFDocumentRef pdf;
}
-(void)drawInContext:(CGContextRef)context;
#end
Implementation file
//PDFViewer.m
#implementation PDFViewer
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
// Initialization code
if(self != nil)
{
CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("WR1MayJun1S08.pdf"), NULL, NULL);
pdf = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
CFRelease(pdfURL);
}
}
return self;
}
-(void)drawInContext:(CGContextRef)context
{
// PDF page drawing expects a Lower-Left coordinate system, so we flip the coordinate system
// before we start drawing.
CGContextTranslateCTM(context, 0.0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Grab the first PDF page
CGPDFPageRef page = CGPDFDocumentGetPage(pdf, 1);
// We're about to modify the context CTM to draw the PDF page where we want it, so save the graphics state in case we want to do more drawing
CGContextSaveGState(context);
// CGPDFPageGetDrawingTransform provides an easy way to get the transform for a PDF page. It will scale down to fit, including any
// base rotations necessary to display the PDF page correctly.
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFCropBox, self.bounds, 0, true);
// And apply the transform.
CGContextConcatCTM(context, pdfTransform);
// Finally, we draw the page and restore the graphics state for further manipulations!
CGContextDrawPDFPage(context, page);
CGContextRestoreGState(context);
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
- (void)dealloc
{
CGPDFDocumentRelease(pdf);
[super dealloc];
}
#end
Now I am adding this class (PDFViewer.h) to my MainViewController.
//MainViewController.m
CGRect frame = CGRectMake(0, 200, 300, 500);
PDFViewer *pdfViewer = [[PDFViewer alloc] initWithFrame:frame];
CGContextRef context = UIGraphicsGetCurrentContext();
[pdfViewer drawInContext:context];
[self.view addSubview:pdfViewer];
It shows nothing. I get the following errors/warnings:
local MultiView[2850] <Error>: CGContextTranslateCTM: invalid context
local MultiView[2850] <Error>: CGContextScaleCTM: invalid context
local MultiView[2850] <Error>: CGContextSaveGState: invalid context
local MultiView[2850] <Error>: CGContextConcatCTM: invalid context
local MultiView[2850] <Error>: CGContextRestoreGState: invalid context
What am I missing?
Regards.

UIGraphicsGetCurrentContext does not return a context if there isn't one, obviously.
You try to get the context at view initialization, at that time there is no context available. A valid context gets pushed onto the stack just before -[UIView drawRect:] is being called. This should work:
//PDFViewer.m
#implementation PDFViewer
- (void)drawRect:(CGRect)rect {
[self drawInContext:UIGraphicsGetCurrentContext()];
}
#end
EDIT
Eventhough I don't like to give anyone copy-and-paste-ready-code, I don't think there is another option left if you didn't understand my latest comment. I don't know what you've tried, but if you try to understand what I'm really saying, this is the only thing you can come up with:
//PDFViewer.m
#implementation PDFViewer
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("WR1MayJun1S08.pdf"), NULL, NULL);
pdf = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
CFRelease(pdfURL);
}
return self;
}
-(void)drawInContext:(CGContextRef)context
{
// PDF page drawing expects a Lower-Left coordinate system, so we flip the coordinate system
// before we start drawing.
CGContextTranslateCTM(context, 0.0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Grab the first PDF page
CGPDFPageRef page = CGPDFDocumentGetPage(pdf, 1);
// We're about to modify the context CTM to draw the PDF page where we want it, so save the graphics state in case we want to do more drawing
CGContextSaveGState(context);
// CGPDFPageGetDrawingTransform provides an easy way to get the transform for a PDF page. It will scale down to fit, including any
// base rotations necessary to display the PDF page correctly.
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFCropBox, self.bounds, 0, true);
// And apply the transform.
CGContextConcatCTM(context, pdfTransform);
// Finally, we draw the page and restore the graphics state for further manipulations!
CGContextDrawPDFPage(context, page);
CGContextRestoreGState(context);
}
- (void)drawRect:(CGRect)rect {
[self drawInContext:UIGraphicsGetCurrentContext()];
}
- (void)dealloc
{
CGPDFDocumentRelease(pdf);
[super dealloc];
}
#end
—
//MainViewController.m
CGRect frame = CGRectMake(0, 200, 300, 500);
PDFViewer *pdfViewer = [[PDFViewer alloc] initWithFrame:frame];
[self.view addSubview:pdfViewer];

I got another simple way to parse PDF for iPhone/iPad:
1.Take one UIwebView (name:pdfView).
2.Give IBoutlet connection to it & Delegate it to FilesOwner
3.In Viewdidload
[self.pdfView loadRequest:[NSURLRequest requestWithURL:[NSURL
fileURLWithPath:[[NSBundle mainBundle]
pathForResource:#"ObjC" ofType:#"pdf"]]]];
4.ObjC.pdf should be in resource folder..

Related

Creating paths in Cocoa

How can can I create a path in Xcode 5 that resembles a half circle. I was able to make the code that goes in the .m file, but I do not know what to do in the .h file for the code that is in the .m file. I followed a tutorial and this is the code I came out with in the .m file.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 4.0);
CGContextSetStrokeColorWithColor(context,
[UIColor blueColor].CGColor);
CGRect rectangle = CGRectMake(60,170,200,80);
CGContextAddEllipseInRect(context, rectangle);
CGContextStrokePath(context);
}
The simplest way to make a view that draws itself as half a circle is to make a view that draws itself as a circle but the view is only half the width of the circle. That way, what is drawn outside the view (the other half of the circle) is not shown.

CALayer doesn't display

(I already read this page, but it didn't help me CALayer not displaying)
I have a class called Image that has this field data:
uint8_t *data;
I already use this data to display this Image on a CALayer that I got gathering code from the internet.
I saw how to create another windows in my application, and I put a NSView inside it to display an Image using the method, I intend to display the histogram latter, but now I'm just trying to display again the same image:
-(void)wrapImageToCALayer: (CALayer*) layer{
if(!layer) return;
CGColorSpaceRef grayColorSpace = CGColorSpaceCreateDeviceGray();
CGContextRef context = CGBitmapContextCreate(data, width, height, 8, step, grayColorSpace, kCGImageAlphaNone);
CGImageRef dstImage = CGBitmapContextCreateImage(context);
dispatch_sync(dispatch_get_main_queue(), ^{
layer.contents = (__bridge id)dstImage;
});
CGImageRelease(dstImage);
CGContextRelease(context);
CGColorSpaceRelease(grayColorSpace);
}
And this is my Window Controler:
#implementation HistogramControllerWindowController
#synthesize display;
- (id)initWithWindow:(NSWindow *)window{
self = [super initWithWindow:window];
if (self) {
// Initialization code here.
}
return self;
}
- (void)windowDidLoad
{
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
[super windowDidLoad];
histogramDisplayLayer = [CALayer layer];
[histogramDisplayLayer setContentsGravity:AVLayerVideoGravityResizeAspectFill];
histogramDisplayLayer.position = CGPointMake(display.frame.size.width/2., display.frame.size.height/2.);
[histogramDisplayLayer setNeedsDisplay];
[display.layer addSublayer: histogramDisplayLayer];
}
#end
And I'm calling this way:
[frame wrapImageToCALayer:histogramDisplayLayer];
Note that histogramDisplayLayer is an external (CALayer *)
Your histogramDisplayLayer have no size defined, you just set a position but not its size. So init its frame, and this should fix your problem.
Just insert:
[display setWantsLayer:YES]; // view's backing store is using a Core Animation Layer
Before:
[display.layer addSublayer: histogramDisplayLayer];
[ Best way to change the background color for an NSView ]

Erase and un erase a image in UIImageVIew with touch using coregraphics

My question is same as mentioned at here. I'm also using two images in my app and all I need is to erase a top image by touch. Then un-erase (if required) the erased part by touch. I'm using following code to erase the the top image. There is also a problem in this approach. Which is that the images are big and I'm using Aspect Fit content mode to properly display them. When I touch on the screen, it erase in the corner not the touched place. I think the touch point calculation is required some fix. Any help will be appreciated.
Second problem is that how to un-erase the erased part by touch?
UIGraphicsBeginImageContext(self.imgTop.image.size);
[self.imgTop.image drawInRect:CGRectMake(0, 0, self.imgTop.image.size.width, self.imgTop.image.size.height)];
self.frame.size.width, self.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
GContextSetLineWidth(UIGraphicsGetCurrentContext(), pinSize);
CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0, 0, 0, 1.0);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeCopy);
CGContextBeginPath(UIGraphicsGetCurrentContext());
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y);
CGContextStrokePath(UIGraphicsGetCurrentContext());
self.imgTop.contentMode = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Your code is quite ambiguous: you're creating a context with imgTop inside, then blending with kCGBlendModeCopy the black color? This would cause the black color to be copied onto imgTop. I assume you wanted to set the layer's contentproperty then?
Anyway this class does what you need. There are only a few interesting methods (they're at the top), the others are only properties or init... routines.
#interface EraseImageView : UIView {
CGContextRef context;
CGRect contextBounds;
}
#property (nonatomic, retain) UIImage *backgroundImage;
#property (nonatomic, retain) UIImage *foregroundImage;
#property (nonatomic, assign) CGFloat touchWidth;
#property (nonatomic, assign) BOOL touchRevealsImage;
- (void)resetDrawing;
#end
#interface EraseImageView ()
- (void)createBitmapContext;
- (void)drawImageScaled:(UIImage *)image;
#end
#implementation EraseImageView
#synthesize touchRevealsImage=_touchRevealsImage, backgroundImage=_backgroundImage, foregroundImage=_foregroundImage, touchWidth=_touchWidth;
#pragma mark - Main methods -
- (void)createBitmapContext
{
// create a grayscale colorspace
CGColorSpaceRef grayscale=CGColorSpaceCreateDeviceGray();
/* TO DO: instead of saving the bounds at the moment of creation,
override setFrame:, create a new context with the right
size, draw the previous on the new, and replace the old
one with the new one.
*/
contextBounds=self.bounds;
// create a new 8 bit grayscale bitmap with no alpha (the mask)
context=CGBitmapContextCreate(NULL,
(size_t)contextBounds.size.width,
(size_t)contextBounds.size.height,
8,
(size_t)contextBounds.size.width,
grayscale,
kCGImageAlphaNone);
// make it white (touchRevealsImage==NO)
CGFloat white[]={1., 1.};
CGContextSetFillColor(context, white);
CGContextFillRect(context, contextBounds);
// setup drawing for that context
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGColorSpaceRelease(grayscale);
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch=(UITouch *)[touches anyObject];
// the new line that will be drawn
CGPoint points[]={
[touch previousLocationInView:self],
[touch locationInView:self]
};
// setup width and color
CGContextSetLineWidth(context, self.touchWidth);
CGFloat color[]={(self.touchRevealsImage ? 1. : 0.), 1.};
CGContextSetStrokeColor(context, color);
// stroke
CGContextStrokeLineSegments(context, points, 2);
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
if (self.foregroundImage==nil || self.backgroundImage==nil) return;
// draw background image
[self drawImageScaled:self.backgroundImage];
// create an image mask from the context
CGImageRef mask=CGBitmapContextCreateImage(context);
// set the current clipping mask to the image
CGContextRef ctx=UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
CGContextClipToMask(ctx, contextBounds, mask);
// now draw image (with mask)
[self drawImageScaled:self.foregroundImage];
CGContextRestoreGState(ctx);
CGImageRelease(mask);
}
- (void)resetDrawing
{
// draw black or white
CGFloat color[]={(self.touchRevealsImage ? 0. : 1.), 1.};
CGContextSetFillColor(context, color);
CGContextFillRect(context, contextBounds);
[self setNeedsDisplay];
}
#pragma mark - Helper methods -
- (void)drawImageScaled:(UIImage *)image
{
// just draws the image scaled down and centered
CGFloat selfRatio=self.frame.size.width/self.frame.size.height;
CGFloat imgRatio=image.size.width/image.size.height;
CGRect rect={0.,0.,0.,0.};
if (selfRatio>imgRatio) {
// view is wider than img
rect.size.height=self.frame.size.height;
rect.size.width=imgRatio*rect.size.height;
} else {
// img is wider than view
rect.size.width=self.frame.size.width;
rect.size.height=rect.size.width/imgRatio;
}
rect.origin.x=.5*(self.frame.size.width-rect.size.width);
rect.origin.y=.5*(self.frame.size.height-rect.size.height);
[image drawInRect:rect];
}
#pragma mark - Initialization and properties -
- (id)initWithCoder:(NSCoder *)aDecoder
{
if ((self=[super initWithCoder:aDecoder])) {
[self createBitmapContext];
_touchWidth=10.;
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
if ((self=[super initWithFrame:frame])) {
[self createBitmapContext];
_touchWidth=10.;
}
return self;
}
- (void)dealloc
{
CGContextRelease(context);
[super dealloc];
}
- (void)setBackgroundImage:(UIImage *)value
{
if (value!=_backgroundImage) {
[_backgroundImage release];
_backgroundImage=[value retain];
[self setNeedsDisplay];
}
}
- (void)setForegroundImage:(UIImage *)value
{
if (value!=_foregroundImage) {
[_foregroundImage release];
_foregroundImage=[value retain];
[self setNeedsDisplay];
}
}
- (void)setTouchRevealsImage:(BOOL)value
{
if (value!=_touchRevealsImage) {
_touchRevealsImage=value;
[self setNeedsDisplay];
}
}
#end
Some notes:
This class retains the two images you need. It has a touchRevealsImage property to set the mode to drawing or erasing, and you can set the width of the line.
At the initialization, it creates a CGBitmapContextRef, grayscale, 8bpp, no alpha, of the same size of the view. This context is used to store a mask, that will be applied to the foreground image.
Every time you move a finger on the screen, a line is drawn on the CGBitmapContextRef using CoreGraphics, white to reveal the image, black to hide it. In this way we're storing a b/w drawing.
The drawRect: routine simply draws the background, then creates a CGImageRef from the CGBitmapContextRef and applies it to the current context as a mask. Then draws the foreground image. To draw images it uses - (void)drawImageScaled:(UIImage *)image, which just draws the image scaled and centered.
If you're planning to resize the view, you should implement a method to copy or to recreate the mask with new size, overriding - (void)setFrame:(CGRect)frame.
The - (void)reset method simply clears the mask.
Even if the bitmap context hasn't any alpha channel, the grayscale color space used has alpha: that's why every time a color is set, I had to specify two components.

Rendering PDF document having 200 pages on iPad

I have to develop an application which takes PDF as input, that PDF document has 200 pages. I am using UIScrollView to swipe left and right. On each swipe I am drawing a PDF document. The code is as under :
- (id)initWithFrame:(CGRect)frame content:(NSString *)aPDFPage type:(NSString *)contentType
{
if ((self = [super initWithFrame:frame]))
{
// Initialization code
self.backgroundColor = [UIColor whiteColor];
pageRef = [[NSString alloc]initWithString:aPDFPage];
pageTypeRef = [[NSString alloc]initWithString:contentType];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
ctx = UIGraphicsGetCurrentContext();
[self drawPDF];
}
-(void)drawPDF
{
NSString *pathToPdfDoc = [[NSBundle mainBundle] pathForResource:self.pageRef ofType:self.pageTypeRef];
NSURL *pdfUrl = [NSURL fileURLWithPath:pathToPdfDoc];
document = CGPDFDocumentCreateWithURL((CFURLRef)pdfUrl);
page = CGPDFDocumentGetPage(document, 1);
CGContextTranslateCTM(ctx, 0.0, [self bounds].size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
CGAffineTransform transform = aspectFit(CGPDFPageGetBoxRect(page, kCGPDFTrimBox),CGContextGetClipBoundingBox(ctx));
CGContextConcatCTM(ctx, transform);
CGContextSetInterpolationQuality(ctx, kCGInterpolationLow);
CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault);
CGContextDrawPDFPage(ctx, page);
CGPDFDocumentRelease(document);
}
-(void)dealloc
{
[pageRef release];
[pageTypeRef release];
[super dealloc];
}
This works fine. But if I swipe very fast, more often subsequent pages does not load instantly. Screen becomes white.
How to solve this, please guide.
Regards,
Ranjan
one of the obvious issues is that drawRect may be called regularly, and may only request that you draw some of the view's rect.
in your implementation, you:
read the pdf from disk. this takes a lot of time, especially since drawRect may be called at a high frequency.
read the pdf. this takes some time -- it can be avoided from occurring during drawRect.
draw one page. draw only what you need to draw, where possible.
dispose of the pdf. you should hold on to the pdf document while the pdf view is visible, rather than reading it from disk every time you need to draw.
Have a look at this thread: Fast and Lean PDF Viewer for iPhone / iPad / iOs - tips and hints?

Cocoa giving error: <Error>: doClip: empty path

Cocoa gives error:
Thu Jun 10 19:13:56 myComputer.local myApp[####] <Error>: doClip: empty path.
But I don't have this function anywhere in my code (can't find by searching in frameworks / project)... Seems a lot of people complain about this because it goes into the console logs, but couldn't find any reason given as to what causes it on a progmatic level.
Any thoughts as to what the problem is?
I would just check to see if the path is empty using: CGContextIsPathEmpty(CGContextRef ctx)
Example:
if(!CGContextIsPathEmpty(c))
CGContextClip(c);
Poking around with class-dump and nm, I found that it's a function in CoreGraphics (Quartz 2D). It's not declared in the headers, so it's a private function.
Break on doClip in the debugger, then move down the stack and see whether any of your code is drawing at that time. If so, you're probably trying to clip to an empty path. If a third-party framework you're using is involved, you should file a bug with its authors.
If you're not calling it (and you shouldn't), and a third-party framework is not implicated, it's probably a bug in one of the Apple frameworks. You should report it to Apple.
I've seen this error before when drawing in a context and trying to clip it. Are you doing any context drawing like this?
CGContextClip(ctx);
CGContextTranslateCTM(ctx, 0, image.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
CGContextDrawImage(ctx, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), image.CGImage);
I had the same issue when I implemented the following methods to set the navigation bar image override specific for landscape orientation. To get rid of the clip error I commented out the CGContextClip portion of the code as shown below:
#implementation UINavigationBar(CustomBackground)
+ (UIImage *) bgImagePortrait
{
static UIImage *image = nil;
if (image == nil) {
image = [[UIImage imageNamed:#"iPhoneHeader_portrait"] retain];
}
return image;
}
+ (UIImage *) bgImageLandscape
{
static UIImage *image = nil;
if (image == nil) {
image = [[UIImage imageNamed:#"iPhoneHeader_landscape"] retain];
}
return image;
}
- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
if ([self isMemberOfClass:[UINavigationBar class]] == NO) {
return;
}
UIImage *image = (self.frame.size.width > 320) ?
[UINavigationBar bgImageLandscape] : [UINavigationBar bgImagePortrait];
//CGContextClip(ctx); // Causes '<Error>: doClip: empty path.' error when changing views.
CGContextTranslateCTM(ctx, 0, image.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
CGContextDrawImage(ctx, CGRectMake(0, 0, self.frame.size.width, self.frame.size.height), image.CGImage);
}
#end