Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Draw image after content in PDF file

I've been searching a lot lately for a good PDF tutorial, documentation, etc.

I ended up using this code, but there are few problems.

Scenario

I have a view which contains a label, a textView and an imageView. Now, we'll call the label name, the textView description and the imageView image.

The name works as header.

The description is very very mutable, it can be from 2 lines to some pages.

Image should go at the end of the description text.

I'm using this code:

- (void)generatePDF{
    NSString *fileName = [NSString stringWithFormat:@"%@.pdf",nameString];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *pdfFileName = [documentsDirectory stringByAppendingPathComponent:fileName];
    
    CFAttributedStringRef currentText = CFAttributedStringCreate(NULL,
                                                                 (CFStringRef)descriptionString, NULL);
    if (currentText) {
        CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
        if (framesetter) {
                        
            // Create the PDF context using the default page: currently constants at the size
            // of 612 x 792.
            UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectZero, nil);
            
            CFRange currentRange = CFRangeMake(0, 0);
            NSInteger currentPage = 0;
            BOOL done = NO;
            
            do {
                // Mark the beginning of a new page.
                UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, kDefaultPageWidth,
                                                          kDefaultPageHeight), nil);
                [self drawHeader]
                // Draw a page number at the bottom of each page
                currentPage++;
                [self drawPageNumber:currentPage];
                
                // Render the current page and update the current range to
                // point to the beginning of the next page.
                currentRange = [self renderPage:currentPage withTextRange:
                                currentRange andFramesetter:framesetter];
                
                // If we're at the end of the text, exit the loop.
                if (currentRange.location == CFAttributedStringGetLength
                    ((CFAttributedStringRef)currentText))
                    done = YES;
            } while (!done);
            
            // Close the PDF context and write the contents out.
            UIGraphicsEndPDFContext();
            
            // Release the framewetter.
            CFRelease(framesetter);
            
        } else {
            NSLog(@"Could not create the framesetter needed to lay out the atrributed string.");
        }
        // Release the attributed string.
        CFRelease(currentText);
    } else {
        NSLog(@"Could not create the attributed string for the framesetter");
    }

}

- (CFRange)renderPage:(NSInteger)pageNum withTextRange:(CFRange)currentRange
       andFramesetter:(CTFramesetterRef)framesetter
{
    // Get the graphics context.
    CGContextRef    currentContext = UIGraphicsGetCurrentContext();
    
    // Put the text matrix into a known state. This ensures
    // that no old scaling factors are left in place.
    CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
    
    // Create a path object to enclose the text. Use 72 point
    // margins all around the text.
    CGRect    frameRect = CGRectMake(22,72, 468, 648);
    CGMutablePathRef framePath = CGPathCreateMutable();
    CGPathAddRect(framePath, NULL, frameRect);
    
    // Get the frame that will do the rendering.
    // The currentRange variable specifies only the starting point. The framesetter
    // lays out as much text as will fit into the frame.
    CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);
    CGPathRelease(framePath);
    
    // Core Text draws from the bottom-left corner up, so flip
    // the current transform prior to drawing.
    CGContextTranslateCTM(currentContext, 0, kDefaultPageHeight);
    CGContextScaleCTM(currentContext, 1.0, -1.0);
    
    // Draw the frame.
    CTFrameDraw(frameRef, currentContext);
    
    // Update the current range based on what was drawn.
    currentRange = CTFrameGetVisibleStringRange(frameRef);
    currentRange.location += currentRange.length;
    currentRange.length = 0;
    CFRelease(frameRef);
    
    return currentRange;
}


- (void)drawPageNumber:(NSInteger)pageNum
{
    NSString* pageString = [NSString stringWithFormat:NSLocalizedString(@"Page", nil), pageNum];
    UIFont* theFont = [UIFont systemFontOfSize:12];
    CGSize maxSize = CGSizeMake(kDefaultPageWidth, 72);
    
    CGSize pageStringSize = [pageString sizeWithFont:theFont
                                   constrainedToSize:maxSize
                                       lineBreakMode:UILineBreakModeClip];
    CGRect stringRect = CGRectMake(((kDefaultPageWidth - pageStringSize.width) / 2.0),
                                   720.0 + ((72.0 - pageStringSize.height) / 2.0) ,
                                   pageStringSize.width,
                                   pageStringSize.height);
    
    [pageString drawInRect:stringRect withFont:theFont];
}

I would like to know how to draw the image at the end of the page, right after the end of the description.

I've drawn the header this way:

-(void)drawHeader{
    NSString *headerString = nameString;
    UIFont* theFont = [UIFont boldSystemFontOfSize:15];
    CGSize maxSize = CGSizeMake(kDefaultPageWidth, 72);
    
    CGSize pageStringSize = [headerString sizeWithFont:theFont constrainedToSize:maxSize lineBreakMode:UILineBreakModeClip];
    CGRect stringRect = CGRectMake(22,22,pageStringSize.width,pageStringSize.height);
    
    [headerString drawInRect:stringRect withFont:theFont];
}

and it's being shown at the beginning of every page.

Now I don't know how to draw the image after the contents (description)!

like image 604
Phillip Avatar asked Jan 21 '13 10:01

Phillip


2 Answers

Well Drawing a pdf in the context is like drawing in a canvas.

The best way to draw text and images which are dynamic in nature is to create and use methods to draw, which returns the height used to draw in the page and simply keeping a variable to calculate the next drawing orgin y position by incrementing with the returned value. Also put a separate offset variable to check the page frame.

To draw an image into pdf context you can make use of the following sample.You can also render CGImage via Core Graphics,but you have to consider rotation of the canvas(CGContext) since those will appear in the upside down way(flipped) since the coordinate origin in Core Graphics is on the bottom right.

UIImage *gymLogo=[UIImage imageNamed:@"logo.png"];
CGPoint drawingLogoOrigin = CGPointMake(5,5);
[gymLogo drawAtPoint:drawingLogoOrigin];
like image 166
Lithu T.V Avatar answered Oct 22 '22 15:10

Lithu T.V


Here's a subclass of UIView that does what you want...

You can place it in a UIScrollView. Call -sizeThatFits: to get the correct frame size during your superview's -layoutSubviews. (also for your enclosing scroll view's contentSize.)

Note that I included a category on UILabel that returns a reasonable size to -sizeThatFits:, you may want to provide your own implementation.

View.h

#import <UIKit/UIKit.h>

@interface View : UIView
@property ( nonatomic, strong, readonly ) UILabel * descriptionLabel ;
@property ( nonatomic, strong, readonly ) UILabel * nameLabel ;
@property ( nonatomic, strong, readonly ) UIImageView * pdfImageView ;

@property ( nonatomic ) CGPDFDocumentRef pdf ;

@end

View.m

#import "View.h"

@implementation UILabel (SizeThatFits)
-(CGSize)sizeThatFits:(CGSize)fitSize
{
    return [ self.text sizeWithFont:self.font constrainedToSize:fitSize ] ;
}
@end

@interface View ()
@property ( nonatomic, strong, readonly ) UIImage * pdfImage ;
@end

@implementation View
@synthesize descriptionLabel = _descriptionLabel ;
@synthesize nameLabel = _nameLabel ;
@synthesize pdfImageView = _pdfImageView ;
@synthesize pdfImage = _pdfImage ;

-(void)dealloc
{
    CGPDFDocumentRelease( _pdf ) ;
}

-(CGSize)sizeThatFits:(CGSize)size
{
    CGSize fitSize = (CGSize){ size.width, CGFLOAT_MAX } ;

    CGSize result = { size.width, 0 } ;
    result.height = [ self.headerLabel sizeThatFits:fitSize ].height
        + [ self.label sizeThatFits:fitSize ].height
        + self.pdfImage.size.height * fitSize.width / self.pdfImage.size.width ;
    return result ;
}

-(void)layoutSubviews
{
    CGRect bounds = self.bounds ;
    CGRect slice ;

    CGRectDivide( bounds, &slice, &bounds, [ self.headerLabel sizeThatFits:bounds.size ].height, CGRectMinYEdge ) ;
    self.headerLabel.frame = slice ;

    CGRectDivide( bounds, &slice, &bounds, [ self.label sizeThatFits:bounds.size ].height, CGRectMinYEdge ) ;
    self.label.frame = slice ;

    self.pdfImageView.frame = bounds ;
}

-(void)setPdf:(CGPDFDocumentRef)pdf
{
    CGPDFDocumentRelease( _pdf ) ;
    _pdf = CGPDFDocumentRetain( pdf ) ;
    _pdfImage = nil ;
}

-(UILabel *)descriptionLabel
{
    if ( !_descriptionLabel )
    {
        UILabel * label = [[ UILabel alloc ] initWithFrame:CGRectZero ] ;
        label.numberOfLines = 0 ;
        //
        // ... configure label here
        //
        [ self addSubview:label ] ;
        _descriptionLabel = label ;
    }
    return _descriptionLabel ;
}

-(UILabel *)nameLabel
{
    if ( !_nameLabel )
    {
        UILabel * label = [[ UILabel alloc ] initWithFrame:CGRectZero ] ;
        label.numberOfLines = 0 ;
        //
        // ... configure label here
        //
        [ self addSubview:label ] ;
        _nameLabel = label ;
    }
    return _nameLabel ;
}

-(UIView *)pdfImageView
{
    if ( !_pdfImageView )
    {
        UIImageView * imageView = [[ UIImageView alloc ] initWithFrame:CGRectZero ] ;
        [ self addSubview:imageView ] ;
        _pdfImageView = imageView ;
    }

    return _pdfImageView ;
}

-(UIImage *)pdfImage
{
    if ( !_pdfImage )
    {
        CGPDFPageRef page = CGPDFDocumentGetPage( self.pdf, 1 ) ; // 1 indexed
        CGRect mediaBox = CGPDFPageGetBoxRect( page, kCGPDFMediaBox ) ;
        UIGraphicsBeginImageContextWithOptions( mediaBox.size, NO, self.window.screen.scale ) ;

        CGContextRef c = UIGraphicsGetCurrentContext() ;
        CGContextScaleCTM( c, 1.0f, -1.0f ) ;
        CGContextTranslateCTM( c, 0.0, -mediaBox.size.height ) ;

        CGContextDrawPDFPage( c, page ) ;

        _pdfImage = UIGraphicsGetImageFromCurrentImageContext() ;

        self.pdfImageView.image = _pdfImage ;

        UIGraphicsEndImageContext() ;
    }

    return _pdfImage ;
}

@end

If you need a page number footer, you can add another UILabel at the bottom, following the same pattern.

like image 33
nielsbot Avatar answered Oct 22 '22 15:10

nielsbot