iospdfios9core-textcfstring

Writing text on pdf ios


Hello I have followed this guide to write text on pdf file. This guide maybe old but follows the same approach as in apple docs

What I have done so far:

PdfCreator.m


const int A4_WIDTH = 612;
const int A4_HEIGHT = 792;

@interface PdfCreator()

@property (nonatomic, assign) double currentHeight;


@end

@implementation PdfCreator


- (void) createPdfWithName:(NSString*)name{
    // Create the PDF context using the default page size of 612 x 792.
    UIGraphicsBeginPDFContextToFile(name, CGRectZero, nil);
    // Mark the beginning of a new page.
    UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, A4_WIDTH, A4_HEIGHT), nil);

    self.currentHeight = 0;
}
- (void) printTrip:(Trip*) trip{

    // 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);

    NSString* textToDraw = @"Hello World";
    CFStringRef stringRef = (__bridge CFStringRef)textToDraw;
    // Prepare the text using a Core Text Framesetter
    CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, NULL);

    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);


    //http://stackoverflow.com/questions/6988498
    CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(
                                                                        framesetter, /* Framesetter */
                                                                        CFRangeMake(0, textToDraw.length), /* String range (entire string) */
                                                                        NULL, /* Frame attributes */
                                                                        CGSizeMake(A4_WIDTH, CGFLOAT_MAX), /* Constraints (CGFLOAT_MAX indicates unconstrained) */
                                                                        NULL /* Gives the range of string that fits into the constraints, doesn't matter in your situation */
                                                                        );

    CGRect frameRect = CGRectMake(0, 0, suggestedSize.width, suggestedSize.height);
    CGMutablePathRef framePath = CGPathCreateMutable();
    CGPathAddRect(framePath, NULL, frameRect);

    // Get the frame that will do the rendering.
    CFRange currentRange = CFRangeMake(0, 0);
    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, 100);
    CGContextScaleCTM(currentContext, 1.0, -1.0);
    // Draw the frame.
    CTFrameDraw(frameRef, currentContext);

    CFRelease(frameRef);
    CFRelease(stringRef);
    CFRelease(framesetter);

}

- (void) endFile{
    UIGraphicsEndPDFContext();
}



@end

Now I use it like this in another model file:

PdfCreator *pdf = [[PdfCreator alloc] init];
[pdf createPdfWithName:documentDirectoryFilename];

for (Trip *t in self.trips) {
    [pdf printTrip:t];
}
[pdf endFile];

NSURL *URL = [NSURL fileURLWithPath:documentDirectoryFilename];

if (URL) {
    // Initialize Document Interaction Controller
    self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:URL];

    // Configure Document Interaction Controller
    [self.documentInteractionController setDelegate:self];

    // Preview PDF
    [self.documentInteractionController presentPreviewAnimated:YES];
}

The problem is that it prints this: enter image description here

I noticed that if I call only once the printTrip: method only one HelloWorld label is printed and in the correct position. Successive calls print the mirrored text on top. It is strange because this line

CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);

should reset the scaling factors. Any help would be appreciated.


Solution

  • Check out the documentation from Apple on CGContextSaveGState and CGContextRestoreGState here: https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CGContext/#//apple_ref/c/func/CGContextSaveGState

    These two functions are commonly used in PDF files to bracket modifications to the current graphic state (which includes everything from color settings to clipping and the CTM or current transformation matrix).

    Using your code:

    CGContextSaveGState(currentContext); /// A
    CGContextTranslateCTM(currentContext, 0, 100);
    CGContextScaleCTM(currentContext, 1.0, -1.0);
    // Draw the frame.
    CTFrameDraw(frameRef, currentContext);
    CGContextRestoreGState(currentContext); /// B
    

    At point B you're now back to exactly where you were at point A.

    You can nest these, it's implemented as a stack. You have to be careful to keep them balanced though. And from the point of view of someone who's written PDF parser software, you also want to keep the number of save / restore pairs to what you actually need. Don't use them unnecessarily :)