What's a right way to draw text in a PDF file? First, I tried:
NSString* string = #"Some Text";
const char* chstr = [string UTF8String];
CGContextRef pdfContext = CGPDFContextCreate(pdfConsumer, &pdfPageRect, NULL);
...
CGContextShowTextAtPoint(pdfContext, x, y, chstr, strlen(chstr));
...
CGContextRelease(pdfContext);
This worked but did not show unicode text correctly. Also the API CGContextShowTextAtPoint has been deprecated in iOS7. So I tried:
NSDictionary *textAttributes = #{NSFontAttributeName: [UIFont systemFontOfSize:8.0]};
...
UIGraphicsPushContext(pdfContext);
[string drawAtPoint:CGPointMake(x, y) withAttributes:textAttributes];
UIGraphicsPopContext();
...
CGContextRelease(pdfContext);
As suggested by some SO posts. But the app crashes on CGContextRelease(pdfContext). If I remove CGContextRelease(pdfContext), it does not draw anything in PDF. Am I missing anything?
** Additional information **
I figured out the crash in CGContextRelease happens only when NSString contains unicode text and here is the image of the call stack at the time of the crash. I even moved the test code of PDF generation in [AppDelegate didFinishLaunchingWithOptions] but it still crashes.
You can try this! I have test it and it work.
//Ready to begin
UIGraphicsBeginPDFContextToFile(tempPath, CGRectZero, nil);
CGPDFPageRef page = CGPDFDocumentGetPage (doc, i);
//grab page i of the PDF
CGRect bounds = [ReaderDocument boundsForPDFPage:page];
//Create a new page
UIGraphicsBeginPDFPageWithInfo(bounds, nil);
CGContextRef context = UIGraphicsGetCurrentContext();
// flip context so page is right way up
CGContextTranslateCTM(context, 0, bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawPDFPage (context, page); // draw the page into graphics context
//Flip back right-side up
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -bounds.size.height);
//Draw text
UIGraphicsPushContext(context);
CGContextSetTextMatrix(context, CGAffineTransformMake(1.0,0.0, 0.0, -1.0, 0.0, 0.0));
CGContextSetTextDrawingMode(context, kCGTextFill); // This is the default
CGContextSetFillColorWithColor(context, [[UIColor blackColor] CGColor]);
CGRect newTextFrame = CGRectMake(100,100,100,30);
[#"Your Text......" drawInRect:newTextFrame withAttributes:#{NSFontAttributeName:self.font}];
UIGraphicsPopContext();
UIGraphicsEndPDFContext();
CGPDFDocumentRelease(doc);
Related
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:
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.
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 :)
The answer is found on part 2 of the tutorial. The context should be reset after the drawing of the text.
CGContextTranslateCTM(currentContext, 0, 100);
CGContextScaleCTM(currentContext, 1.0, -1.0);
// Draw the frame.
CTFrameDraw(frameRef, currentContext);
/*NEW!*/
CGContextScaleCTM(currentContext, 1.0, -1.0);
I want to rotate my NSString that's at a set location so that it reads from bottom to up.
I know the issue lies in the CGContextTranslateCTM, but I'm not sure how to fix it. If I comment this line, I see my text where I want it, just not rotated. This must be moving my NSString to where it is not visible.
Thoughts?
Here's what I have tried:
NSString * xString = [NSString stringWithFormat:#"%#", self.xStringValue];
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor blackColor] CGColor]);
CGContextSaveGState(UIGraphicsGetCurrentContext());
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), 0, 0);
CGContextRotateCTM(UIGraphicsGetCurrentContext(), M_PI/2);
[xString drawInRect:(CGRectMake(x, y, width, height) withFont:self.Font];
CGContextRestoreGState(UIGraphicsGetCurrentContext());
EDIT:
The code above is drawing my NSString values, but they are being positioned off screen where I can not see them.
This code is actually getting them on screen, just not in the right place.
X and Y are both calculated distances.
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGAffineTransform transform = CGAffineTransformMakeRotation(-90.0 * M_PI/180.0);
CGContextConcatCTM(context, transform);
CGContextTranslateCTM(context, -rect.size.height-x, rect.size.height-y);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor blackColor] CGColor]);
[xString drawInRect:CGRectMake(x, y, w, h) withFont:self.textFont];
CGContextRestoreGState(context);
}
EDIT THREE:
Solution:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, point.x, point.y);
CGAffineTransform textTransform = CGAffineTransformMakeRotation(-1.57);
CGContextConcatCTM(context, textTransform);
CGContextTranslateCTM(context, -point.x, -point.y);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [[UIColor whiteColor] CGColor]);
[string drawInRect:CGRectMake(x, y, width, height) withFont:font];
CGContextRestoreGState(context);
If you want to rotate a UILabel vertically (90°) you can do :
[_myLabel setTransform:CGAffineTransformMakeRotation(-M_PI / 2)];
The result for a UILabel :
Is there any way to generate high resolution PDFs on ios that support retina display? I made a basic PDF generator but on my iPad 3 it still looks pixelated. Thank you for all kind of advices!
Update:
In my PDF file text are smooth and beautiful. But when I use code to draw this pdf i got pixelated on retina display (drawing code is in a bottom).
PDF generation:
NSString *fileName = [self.bookManager cacheBookFileForPage:i];
UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
CGContextTranslateCTM(currentContext, 0, 100);
CGContextScaleCTM(currentContext, 1.0, -1.0);
// TODO: Temporary numbers
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 768 * 2, 1024 * 2), nil);
NSString *textToDraw = #"text";
CFStringRef stringRef = (__bridge CFStringRef)textToDraw;
CTFontRef ctFont= CTFontCreateWithName(CFSTR("Arial"), 20 * 2, &CGAffineTransformIdentity);
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge id)ctFont, (NSString *)kCTFontAttributeName,
nil];
NSAttributedString *string = [[NSAttributedString alloc] initWithString:textToDraw
attributes:attributes];
CFRelease(ctFont);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)string);
// TODO: temporary
CGRect frameRect = CGRectMake(100, 100, 800 * 2, 50 * 2);
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);
// Draw the frame.
CTFrameDraw(frameRef, currentContext);
CFRelease(frameRef);
CFRelease(stringRef);
CFRelease(framesetter);
// Close the PDF context and write the contents out.
UIGraphicsEndPDFContext();
PDF Drawing:
// This will return pageRef from PDF
CGPDFPageRef page = [self.bookManager contentForPage:index + 1];
CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 1.0f);
CGContextFillRect(context, CGContextGetClipBoundingBox(context));
CGContextTranslateCTM(context, 0.0f, [self.view.layer bounds].size.height);
CGRect transformRect = CGRectMake(0, 0, [self.view.layer bounds].size.width, [self.view.layer bounds].size.height);
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFCropBox, transformRect, 0, true);
// And apply the transform.
CGContextConcatCTM(context, pdfTransform);
CGContextScaleCTM(context, 1.0f, -1.0f);
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGContextSetRenderingIntent(context, kCGRenderingIntentDefault);
CGContextDrawPDFPage(context, page);
I think one of the problems you have is that you are scaling what you are drawing; don't use that transform! Just create your pdf context and draw natively in that. Your fonts will look crisp as they are now drawn in full resolution, instead of half rez and then the bitmap of that font is scaled up, which of course introduces jaggies.
tl;dr: draw directly to the pdf, don't use the scaling transform on text.
I have a couple of questions.
I have a loaded UIImage and i would like to:
1st - draw another image onto my loaded UIImage
2nd - draw a line (with color and thickness) onto my loaded UIImage
I would greatly appreciate if you would come up with some basic stuff, i'm still a noob :)
There's another alternative and it comes using core-graphics.
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
UIImage *bottomImage = ...;
CGRect bottomImageRect = ...;
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -bottomImageRect.size.height);
CGContextDrawImage(context, bottomImageRect, bottomImage.CGImage);
CGContextRestoreGState(context);
CGContextSaveGState(context);
UIImage *topImage = ...;
CGRect topImageRect = ...;
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -topImageRect.size.height);
CGContextDrawImage(context, topImageRect, topImage.CGImage);
CGContextRestoreGState(context);
CGPoint origin = ...;
CGPoint end = ...;
CGContextMoveToPoint(context, origin.x, origin.y);
CGContextAddLineToPoint(context, end.x, end.y);
CGContextSetStrokeColorWithColor(context, [UIColor whatever].CGColor);
CGContextStrokePath(context);
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
EDIT:
Changed the code a little so that images are not inverted
I have attached the code snippet that I am using for displaying a PDF. The following code displays the PDF, but it seems that it is either squeezed or not using the full size of the iPad display, resulting in a page that is too small.
How can I display a PDF that fits in the boundary of an iPad display or in a zoomed state? I have tried using a different approach (approach-2), but it creates a problem with the PDF appearing rotated at a 90-degree angle.
Approach-1:
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, 0.0, [self.view bounds].size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
CGContextConcatCTM(ctx,
CGPDFPageGetDrawingTransform(page, kCGPDFCropBox,
[self.view bounds], 0, true));
CGContextDrawPDFPage(ctx, page);
CGContextRestoreGState(ctx);
Approach-2:
CGPDFPageRef page = CGPDFDocumentGetPage(pdfdocument, PageNo+1);
if(page){
CFRetain(page);
}
CGRect pageRect =CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
int angle= CGPDFPageGetRotationAngle(page);
float pdfScale = self.bounds.size.width/pageRect.size.width;
CGContextSetRGBFillColor(context, 1.0,1.0,1.0,1.0);
CGContextFillRect(context,self.bounds);
CGContextSaveGState(context);
// Flip the context so that the PDF page is rendered
// right side up.
CGContextTranslateCTM(context, 0.0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Scale the context so that the PDF page is rendered
// at the correct size for the zoom level.
CGContextScaleCTM(context, pdfScale,pdfScale);
CGContextDrawPDFPage(context, page);
CGContextRestoreGState(context);
Can anyone suggest to me a solution that allows any PDF of any size and any angle to be displayed full screen on an iPad in both orientations? It would be great if you can provide me a code snippet or pseudo-code.
Thanks
Hopefully this helps. It shows how to render the first page of a PDF referenced by an URL.
This code is a collection of snippets of my own codebase so don't just copy paste it in 1 file and expect it to build and run. I put some comments so you see what belongs where, and you'll need to declare some ivars for it to work.
// helper function
CGRect CGRectScaleAspectFit(CGRect sourceRect,CGRect fitRect)
{
if ( sourceRect.size.width > fitRect.size.width)
{
float scale = fitRect.size.width / sourceRect.size.width;
sourceRect.size.width = fitRect.size.width;
sourceRect.size.height = (int)(sourceRect.size.height * scale);
}
if ( sourceRect.size.height > fitRect.size.height)
{
float scale = fitRect.size.height / sourceRect.size.height;
sourceRect.size.height = fitRect.size.height;
sourceRect.size.width = (int)(sourceRect.size.width * scale);
}
return sourceRect;
}
// in your UIView subclass init method
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
pdfPage = CGPDFDocumentGetPage(pdf, 1);
CGRect fitrect = CGRectMake(0, 0, self.frame.size.width,self.frame.size.height);
CGRect pageRect = CGPDFPageGetBoxRect(pdfPage, kCGPDFMediaBox);
CGRect f;
f.origin.x=0;
f.origin.y=0;
f.size.height = self.frame.size.height;
f.size.width = self.frame.size.height * pageRect.size.width/pageRect.size.height;
f = CGRectScaleAspectFit(f,fitrect); // this is the actual pdf frame rectangle to fill the screen as much as possible
pdfScale = f.size.height/pageRect.size.height;
// in your UIView subclass drawRect method
CGContextSetRGBFillColor(context, 1.0,1.0,1.0,1.0);
CGContextFillRect(context,self.bounds);
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextScaleCTM(context, pdfScale,pdfScale);
CGContextDrawPDFPage(context, pdfPage);
CGContextRestoreGState(context);
This will display your PDF Full Screen : not sure this is the best way to handle PDF though
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
//PDF might be transparent, assume white paper - set White Background
[[UIColor whiteColor] set];
CGContextFillRect(ctx, rect);
//Flip coordinates
CGContextGetCTM(ctx);
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -rect.size.height);
//PDF File Path
NSURL *pdfURL = [[NSBundle mainBundle] URLForResource:#"TEST" withExtension:#"pdf"];
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((__bridge CFURLRef)pdfURL);
CGPDFPageRef page1 = CGPDFDocumentGetPage(pdf, 1);
//Get the rectangle of the cropped inside
CGRect mediaRect = CGPDFPageGetBoxRect(page1, kCGPDFCropBox);
CGContextScaleCTM(ctx, rect.size.width / mediaRect.size.width,
rect.size.height / mediaRect.size.height);
//Draw PDF
CGContextDrawPDFPage(ctx, page1);
CGPDFDocumentRelease(pdf);
}
Copy the PDF you want to display into your project library,
in the example above the name of the PDF file is "TEST" : you want to name yours with the name of your file
This works for me to display the PDF in full screen within a UIView,
I am not sure this is your best option though : there are issues : i.e. you need to handle zooming, and if you do that, you need to handle panning. Also Orientation is a big mess (when you flip the device to landscape mode) the file loses its Aspect Ration (and gets squished)
Play a round with it .....
Though jumping into PDF handling in IOS is nothing but a pain . . . .
Hope this helps you out a little