I have a long text content and wanted to convert it in to a multipage PDF file.
I have followed the instructions provided in this tutorial http://www.raywenderlich.com/6581/how-to-create-a-pdf-with-quartz-2d-in-ios-5-tutorial-part-1.
But I am struggling to make the pdf multi page.
The code I'm using can create single page PDF.
+(void)drawText
{
NSString* textToDraw = #"The sample text";
CFStringRef stringRef = (__bridge CFStringRef)textToDraw;
// Prepare the text using a Core Text Framesetter
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, NULL);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
CGRect frameRect = CGRectMake(50, 50, 512, 1000);
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);
// 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);
// 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)drawPDF:(NSString*)fileName
{
// Create the PDF context using the default page size of 612 x 792.
UIGraphicsBeginPDFContextToFile(fileName, CGRectZero, nil);
// Mark the beginning of a new page.
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
[self drawText];
// Close the PDF context and write the contents out.
UIGraphicsEndPDFContext();
}
you can draw pdf using following code:
// Use Core Text to draw the text in a frame on the page.
- (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(72, 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:#"Page %ld", (long)pageNum];
UIFont* theFont = [UIFont systemFontOfSize:12];
// CGSize maxSize = CGSizeMake(kDefaultPageWidth, 72);
CGSize pageStringSize = [pageString sizeWithAttributes:
#{NSFontAttributeName:
theFont}];
// CGSize pageStringSize = [pageString sizeWithFont:theFont
// constrainedToSize:maxSize
// lineBreakMode:NSLineBreakByClipping];
CGRect stringRect = CGRectMake(((kDefaultPageWidth - pageStringSize.width) / 2.0),
720.0 + ((72.0 - pageStringSize.height) / 2.0) ,
pageStringSize.width,
pageStringSize.height);
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
NSDictionary *attributes = #{NSFontAttributeName: [UIFont systemFontOfSize:15], NSParagraphStyleAttributeName: paragraphStyle};
[pageString drawInRect:stringRect withAttributes:attributes];
// [pageString drawInRect:stringRect withFont:theFont];
}
For save pdf:
- (IBAction)savePDFFile:(id)sender
{
NSString* path = [[NSBundle mainBundle] pathForResource:#"sampleData" ofType:#"plist"];
// get a temprorary filename for this PDF
path = NSTemporaryDirectory();
self.pdfFilePath = [path stringByAppendingPathComponent:
[NSString stringWithFormat:#"%d.pdf",
[[NSDate date]
timeIntervalSince1970] ]];
// Prepare the text using a Core Text Framesetter
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL,
(CFStringRef)textView.text, NULL);
if (currentText) {
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
if (framesetter) {
NSString* pdfFileName = self.pdfFilePath; //[NSString stringWithString:#"test.pdf"];
// 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);
// 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");
}
// Ask the user if they'd like to see the file or email it.
UIActionSheet* actionSheet = [[[UIActionSheet alloc] initWithTitle:#"Would you like to preview or email this PDF?"
delegate:self
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:#"Preview", #"Email", nil] autorelease];
[actionSheet showInView:self.view];
}
here it is full demo please check: http://ge.tt/1eW8iJt1/v/0
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);
EDIT: Here's a Github Gist of the below code if that is more readable.
I'm writing a method to draw text within a rectangle in a PDF using Core Text. What I've written accomplishes everything I need, except vertical alignment (top, center, bottom). The standard way that the text is currently drawn is the top, and I specifically need the bottom alignment.
- (void)drawText:(NSString *)textToDraw inFrame:(CGRect)frameRect withFont:(UIFont *)originalFont textColor:(UIColor *)textColor alignment:(PDFTextAlignment)alignment verticalAlignment:(PDFTextVerticalAlignment)verticalAlignment {
if (!textToDraw) {
// If nil, give it an empty value to draw
textToDraw = #"";
}
// Prepare font
CTFontRef font = [self ctFontRefFromUIFont:originalFont];
CGColorRef color = textColor.CGColor;
// Paragraph
CTTextAlignment ctAlignment;
switch (alignment) {
case PDFTextAlignmentLeft:
ctAlignment = kCTTextAlignmentLeft;
break;
case PDFTextAlignmentCenter:
ctAlignment = kCTTextAlignmentCenter;
break;
case PDFTextAlignmentRight:
ctAlignment = kCTTextAlignmentRight;
break;
case PDFTextAlignmentJustified:
ctAlignment = kCTTextAlignmentJustified;
break;
default:
ctAlignment = kCTTextAlignmentLeft;
break;
}
CTParagraphStyleSetting settings[] = {
{kCTParagraphStyleSpecifierAlignment, sizeof(ctAlignment), &ctAlignment},
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(settings, sizeof(settings) / sizeof(settings[0]));
// Create an attributed string
CFStringRef keys[] = { kCTFontAttributeName, kCTForegroundColorAttributeName, kCTParagraphStyleAttributeName };
CFTypeRef values[] = { font, color, paragraphStyle };
CFDictionaryRef attr = CFDictionaryCreate(NULL, (const void **)&keys, (const void **)&values,
sizeof(keys) / sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFStringRef stringRef = (__bridge CFStringRef)textToDraw;
// Prepare the text using a Core Text Framesetter.
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, attr);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
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);
// 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);
// Core Text draws from the bottom-left corner up, so flip
// the current transform prior to drawing.
// Modify this to take into consideration the origin.
CGContextTranslateCTM(currentContext, 0, frameRect.origin.y*2);
CGContextScaleCTM(currentContext, 1.0, -1.0);
// Draw the frame.
CTFrameDraw(frameRef, currentContext);
// Add these two lines to reverse the earlier transformation.
CGContextScaleCTM(currentContext, 1.0, -1.0);
CGContextTranslateCTM(currentContext, 0, (-1)*frameRect.origin.y*2);
CFRelease(frameRef);
CFRelease(stringRef);
CFRelease(framesetter);
CFRelease(font);
CFRelease(paragraphStyle);
}
Here is an example of how this will currently draw text in a rectangle (note the rectangle drawn with the lines is the same frameRect I'm passing in)...
.
I want this text to appear as bottom aligned in this rectangle, like this...
.
I've gone through the solutions on this SO post, but have not had any luck getting the text to align correctly.
I'm passing in an enumerated value for the vertical alignment, so ideally I will determine the vertical alignment with the following conditional.
if (verticalAlignment == PDFTextVerticalAlignmentTop) {
// Top align
} else if (verticalAlignment == PDFTextVerticalAlignmentCenter) {
// Center (vertical) align
} else if (verticalAlignment == PDFTextVerticalAlignmentBottom) {
// Bottom align
} else {
// Default: Bottom alignment
}
Thanks!
As I am not quite sure how you approached the solutions from the linked post, I will suggest a solution based on one of those answers. This may be exactly what you tried, but it may also be the appropriate solution.
I assume a method exists for obtaining the size of CTFrameRef, leaving that implementation to you, as long as the appropriate CGSize is returned. Apart from the methods shown in the linked SO post, it might also be worth looking at CTFramesetterSuggestFrameSizeWithConstraints. Assuming this function is available, it should be enough to adjust this part of the drawing function
...
CGMutablePathRef framePath = CGPathCreateMutable();
// Get the graphics context.
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGRect textRect = frameRect; // Will also give you the default behavior for top align
CGSize textSize = [self measureFrame:frameref forContext:currentContext];
if (verticalAlignment == PDFTextVerticalAlignmentBottom) {
// Bottom align
textRect.size.height = textSize.height;
textRect.origin.y = frameRect.origin.y + frameRect.size.height - textSize.height;
} else if (verticalAlignment == PDFTextVerticalAlignmentCenter) {
// Center (vertical) align
textRect.size.height = textSize.height;
textRect.origin.y = frameRect.origin.y + (frameRect.size.height - textSize.height) / 2;
}
CGPathAddRect(framePath, NULL, textRect);
...
After going through several blogs & forums I didn't find an appropriate solution for drawing inclined/angled text using core Text on a views context.
So here is how it goes.
I have a view whose - (void)drawRect:(CGRect)rect is invoked to draw a string (multi or single line text) on screen.
CODE:
- (void)drawRect:(CGRect)rect
{
NSString *text = #"This is some text being drawn by CoreText!\nAnd some more text on another line!";
//Core Text (Create Attributed String)
UIColor *textColor = [UIColor blackColor];
CGColorRef color = textColor.CGColor;
CTFontRef font = CTFontCreateWithName((CFStringRef) #"HelveticaNeue", 20.0, NULL);
CTTextAlignment theAlignment = kCTTextAlignmentLeft;
CFIndex theNumberOfSettings = 1;
CTParagraphStyleSetting theSettings[1] =
{
{ kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment),
&theAlignment }
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(theSettings, theNumberOfSettings);
NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
CFBridgingRelease(font), (NSString *)kCTFontAttributeName,
color, (NSString *)kCTForegroundColorAttributeName,
paragraphStyle, (NSString *) kCTParagraphStyleAttributeName,
nil];
NSAttributedString *stringToDraw = [[NSAttributedString alloc] initWithString:text attributes:attributesDict];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)stringToDraw);
//Create Frame
CGMutablePathRef path = CGPathCreateMutable();
CGAffineTransform transform = CGAffineTransformMakeScale(1, -1);
//First translate your image View according to transform
transform = CGAffineTransformTranslate(transform, 0, - self.bounds.size.height);
// Then whenever you want any point according to UIKit related coordinates apply this transformation on the point or rect.
CGRect frameText = CGRectMake(60, 100, 200, 200);
CGRect newRectForUIKit = CGRectApplyAffineTransform(frameText, transform);
CGPathAddRect(path, NULL, newRectForUIKit);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
CGContextTranslateCTM(ctx, 0, ([self bounds]).size.height );
CGContextScaleCTM(ctx, 1.0, -1.0);
//Draw Frame
CTFrameDraw(frame, ctx);
//Release all retained objects
CFRelease(path);
}
Output:
Apart from drawing text I want to add an angle to the entire drawn text. Something like this(Required output)
So how do I add an rotation angle to the drawn text in core text?
Note: 1)A single context can have multiple drawn text objects with their respective angles as shown below
I hope my question is clear.
Apply the rotation to the context before drawing the CTFrameRef into it.
Edit :
If you want multiple angles, you need to save/restore the graphics states, each time.
Something like :
- (void)drawRect:(CGRect)rect
{
NSString *text = #"This is some text being drawn by CoreText!\nAnd some more text on another line!";
//Core Text (Create Attributed String)
UIColor *textColor = [UIColor blackColor];
CGColorRef color = textColor.CGColor;
CTFontRef font = CTFontCreateWithName((CFStringRef) #"HelveticaNeue", 20.0, NULL);
CTTextAlignment theAlignment = kCTTextAlignmentLeft;
CFIndex theNumberOfSettings = 1;
CTParagraphStyleSetting theSettings[1] =
{
{ kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment),
&theAlignment }
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(theSettings, theNumberOfSettings);
NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
CFBridgingRelease(font), (NSString *)kCTFontAttributeName,
color, (NSString *)kCTForegroundColorAttributeName,
paragraphStyle, (NSString *) kCTParagraphStyleAttributeName,
nil];
NSAttributedString *stringToDraw = [[NSAttributedString alloc] initWithString:text attributes:attributesDict];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)stringToDraw);
//Create Frame
CGMutablePathRef path = CGPathCreateMutable();
CGRect frameText = CGRectMake(60, 100, 200, 200);
CGPathAddRect(path, NULL, frameText);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx); /* save Graphic State for context rotation */
// transform (rotate) context
CGAffineTransform transform = CGAffineTransformMakeTranslation(self.bounds.size.width / 2.f, self.bounds.size.height / 2.f);
CGFloat rotation = -M_PI / 2.f;
transform = CGAffineTransformRotate(transform,rotation);
transform = CGAffineTransformTranslate(transform,-self.bounds.size.width / 2.f, -self.bounds.size.height / 2.f);
CGContextConcatCTM(ctx, transform);
CGContextSaveGState(ctx);
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
CGContextTranslateCTM(ctx, 0, ([self bounds]).size.height );
CGContextScaleCTM(ctx, 1.0, -1.0);
//Draw Frame
CTFrameDraw(frame, ctx);
//Release all retained objects
CFRelease(path);
CGContextRestoreGState(ctx);
CGContextRestoreGState(ctx); /* restore Graphic State for context rotation */
CGContextSaveGState(ctx); /* save Graphic States for another drawing */
/* lets draw another string with different angle */
attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
CFBridgingRelease(CTFontCreateWithName((CFStringRef) #"HelveticaNeue", 14.0, NULL)), (NSString *)kCTFontAttributeName,
[UIColor yellowColor].CGColor, (NSString *)kCTForegroundColorAttributeName,
nil];
stringToDraw = [[NSAttributedString alloc] initWithString:#"another piece of text to drawn on same context with no angle" attributes:attributesDict];
framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)stringToDraw);
path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(60, -100, 200, 200));
frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
CFRelease(path);
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
CGContextTranslateCTM(ctx, 0, ([self bounds]).size.height );
CGContextScaleCTM(ctx, 1.0, -1.0);
CTFrameDraw(frame, ctx);
/**
* #note don't forget to restore a previously saved GState, or this will be source of problems
*/
CGContextRestoreGState(ctx);
CFRelease(frame);
CFRelease(framesetter);
}
I am creating dynamic PDF from my application. In some cases i want my text to be write in PDF with desired color. how can i get that?
I am using CoreText.
Here is my code to draw text in my PDF,
+(void)drawText:(NSString*)textToDraw inFrame:(CGRect)frameRect
{
frameRect.origin.y = frameRect.origin.y + frameRect.size.height; // New line
CFStringRef stringRef = ( CFStringRef)textToDraw;
CGColorSpaceCreateWithName(stringRef);
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, NULL);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
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);
// 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);
// Core Text draws from the bottom-left corner up, so flip
// the current transform prior to drawing.
CGContextTranslateCTM(currentContext, 0, frameRect.origin.y*2);
CGContextScaleCTM(currentContext, 1.0, -1.0);
// Draw the frame.
CTFrameDraw(frameRef, currentContext);
CGContextScaleCTM(currentContext, 1.0, -1.0);
CGContextTranslateCTM(currentContext, 0, (-1)*frameRect.origin.y*2);
CFRelease(frameRef);
//CFRelease(stringRef);
CFRelease(framesetter);
}
Any help or suggestion will be appreciated.,
Thanks in advance.
If you are using coretext, try
CTFontRef font = CTFontCreateWithName((CFStringRef)#"Helvetica", 16.0f, nil);
CFAttributedStringSetAttribute(textString,CFRangeMake(0, strLength-1), kCTFontAttributeName, font);
You can also try CGContextSetFont
NSString *fontName = #"Helvetica";
CGFontRef fontRef = CGFontCreateWithFontName((__bridge CFStringRef)fontName);
CGContextSetFont(context, fontRef);
CGContextSetFontSize(context, 30);
See my question, you will get an idea.
I am trying to draw slightly rotated text with Core Graphics on the iOS platform. Text renders fine when not rotating but the rendering system tries to lock onto pixels for rotated text.
For example: If you rotate a Core Graphics context by some small amount (like 2 degrees) and then draw text the individual characters will appear to jump up and down as Core Graphics locks the characters to the pixels (font hinting). I know that the text may become blurry if it would not lock onto the pixel grid but that's acceptable. Jumping characters are not. So how can I disable vertical font hinting? Hinting horizontal would be ok, but turning it all off is ok too.
Code for custom UIView:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
[self.backgroundColor setFill];
CGContextFillRect(context, rect);
CGContextSaveGState(context);
// rotate context
CGContextTranslateCTM(context, self.bounds.size.width / 2.0, self.bounds.size.width / 2.0);
CGContextRotateCTM(context, 2.0 * M_PI / 180.0);
CGContextTranslateCTM(context, -self.bounds.size.width / 2.0, -self.bounds.size.width / 2.0);
[[UIColor blackColor] setFill];
[self.title drawInRect:[self bounds] withFont:[UIFont systemFontOfSize:15.0]];
CGContextRestoreGState(context);
}
Result (not exactly of this code but similar, red lines inserted to guide to the "error"):
The only solution I have found is to get the actual Bezier paths of the glyphs with Core Text and draw those, which circumvents any vertical hinting. The following code excerpt is rather lengthy:
CGRect textRect = CGRectMake(0.0, 0.0, 300.0, 190.0);
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip coordinate system vertically.
CGContextSaveGState(context);
CGFloat rectHeight = textRect.size.height;
CGContextTranslateCTM(context, 0.0, rectHeight);
CGContextScaleCTM(context, 1.0f, -1.0f);
// Positive degrees because of flip.
CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(2.0 * M_PI/180.0);
CGContextConcatCTM(context, rotationTransform);
CGFloat pointSize = 15.0;
CTFontRef font = CTFontCreateUIFontForLanguage(kCTFontSystemFontType,
pointSize,
NULL);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
NSDictionary *initialAttributes = (
#{
(NSString *)kCTFontAttributeName : (__bridge id)font,
(NSString *)kCTForegroundColorAttributeName : (__bridge id)[UIColor blackColor].CGColor
}
);
NSMutableAttributedString *attributedString =
[[NSMutableAttributedString alloc] initWithString:[self string]
attributes:initialAttributes];
//
// For typesetting a frame, we should create a paragraph style.
// Includes fix for CTFramesetter’s wrong line spacing behavior.
// See Technical Q&A QA1698: “How do I work-around an issue where some lines
// in my Core Text output have extra line spacing?”
//
// Center alignment looks best when filling an ellipse.
CTTextAlignment alignment = kCTLeftTextAlignment;
CTLineBreakMode lineBreakMode = kCTLineBreakByWordWrapping;
// This is the leading in the historical sense, which is added to the point
// size but does not include it like the line height does.
CGFloat leading = 2.0;
// Still, for the fix we do need the line height.
CGFloat lineHeight = pointSize + leading;
CTParagraphStyleSetting paragraphStyleSettings[] =
{
{
kCTParagraphStyleSpecifierAlignment,
sizeof(alignment),
&alignment
},
{
kCTParagraphStyleSpecifierLineBreakMode,
sizeof(lineBreakMode),
&lineBreakMode
},
// These two specifiers fix the line spacing when set to line height.
{
kCTParagraphStyleSpecifierMinimumLineHeight,
sizeof(lineHeight),
&lineHeight
},
{
kCTParagraphStyleSpecifierMaximumLineHeight,
sizeof(lineHeight),
&lineHeight
}
// Very important: Do not set kCTParagraphStyleSpecifierLineSpacing too,
// or it will be added again!
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(
paragraphStyleSettings,
sizeof(paragraphStyleSettings) / sizeof(paragraphStyleSettings[0])
);
// Apply paragraph style to entire string. This cannot be done when the
// string is empty, by the way, because attributes can only be applied to
// existing characters.
NSRange stringRange = NSMakeRange(0, [attributedString length]);
[attributedString addAttribute:(NSString *)kCTParagraphStyleAttributeName
value:(__bridge id)(paragraphStyle)
range:stringRange];
// Create bezier path to contain our text.
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, textRect);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attributedString));
// Range with length 0 indicates that we want to typeset until we run out of
// text or space.
CTFrameRef frame = CTFramesetterCreateFrame(
framesetter,
CFRangeMake(0, 0),
path,
NULL
);
CFArrayRef lines = CTFrameGetLines(frame);
CFIndex lineCount = CFArrayGetCount(lines);
CFRange range = CFRangeMake(0, 0);
CGPoint lineOrigins[lineCount];
CTFrameGetLineOrigins(frame, range, lineOrigins);
for (NSUInteger lineIndex = 0; lineIndex < lineCount; ++lineIndex)
{
CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);
CGPoint lineOrigin = lineOrigins[lineIndex];
CFArrayRef runs = CTLineGetGlyphRuns(line);
CFIndex runCount = CFArrayGetCount(runs);
for (NSUInteger runIndex = 0; runIndex < runCount; ++runIndex)
{
CTRunRef run = CFArrayGetValueAtIndex(runs, runIndex);
CFIndex glyphCount = CTRunGetGlyphCount(run);
CGGlyph glyphBuffer[glyphCount];
CTRunGetGlyphs(run, range, glyphBuffer);
CGPoint positionsBuffer[glyphCount];
CTRunGetPositions(run, range, positionsBuffer);
for (NSUInteger glyphIndex = 0; glyphIndex < glyphCount; ++glyphIndex)
{
CGGlyph glyph = glyphBuffer[glyphIndex];
CGPoint position = positionsBuffer[glyphIndex];
CGAffineTransform positionTransform = CGAffineTransformMakeTranslation(lineOrigin.x + position.x,
lineOrigin.y + position.y);
CGPathRef glyphPath = CTFontCreatePathForGlyph(font, glyph, &positionTransform);
CGContextAddPath(context, glyphPath);
}
}
}
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGContextFillPath(context);
CFRelease(font);
CFRelease(framesetter);
// Use specialized release function when it exists.
CGPathRelease(path);
CGContextRestoreGState(context);