I'm using the code below to write text to a pdf. This is pretty standard stuff, using attributes to style the text.
Use of attributes works but I need to be able to shrink the text to fit the available frame, similar to a UILabel. There doesn't appear to be any kind of minimum font size setting, which probably makes sense as attributed text can be variable in font and sizes.
So, is there a way to measure the proposed size of the text when outputting to pdf to see if it will fit the frame, consequently allowing me to reduce my font size attributes manually?
-(void)drawText:(NSString*)text inFrame:(CGRect)frameRect withAttributes:(NSDictionary*)attributes
{
CFStringRef stringRef = (__bridge CFStringRef)text;
CFDictionaryRef attributeRef = (__bridge CFDictionaryRef)attributes;
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, attributeRef);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
CGMutablePathRef framePath = CGPathCreateMutable();
CGPathAddRect(framePath, NULL, frameRect);
CFRange currentRange = CFRangeMake(0, 0);
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, currentRange, framePath, NULL);
CGPathRelease(framePath);
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
CGFloat offset = (frameRect.origin.y*2)+frameRect.size.height;
CGContextTranslateCTM(currentContext, 0, offset);
CGContextScaleCTM(currentContext, 1.0, -1.0);
CTFrameDraw(frameRef, currentContext);
CGContextScaleCTM(currentContext, 1.0, -1.0);
CGContextTranslateCTM(currentContext, 0, -offset);
CFRelease(frameRef);
CFRelease(framesetter);
CFRelease(currentText);
}
Related
I'm using a slight modification of code from this excellent article from rawenderlich.com to write some text to a PDF:
-(void)drawText:(NSString*)text inFrame:(CGRect)frameRect withAttributes:(NSDictionary*)attributes
{
CFStringRef stringRef = (__bridge CFStringRef)text;
// Prepare the text using a Core Text Framesetter
CFDictionaryRef attributeRef = (__bridge CFDictionaryRef)attributes;
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, stringRef, attributeRef);
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.
CGFloat offset = (frameRect.origin.y*2)+frameRect.size.height;
CGContextTranslateCTM(currentContext, 0, offset);
CGContextScaleCTM(currentContext, 1.0, -1.0);
// Draw the frame.
CTFrameDraw(frameRef, currentContext);
CGContextScaleCTM(currentContext, 1.0, -1.0);
CGContextTranslateCTM(currentContext, 0, -offset);
CFRelease(frameRef);
CFRelease(framesetter);
CFRelease(currentText);
}
This works well, but I cannot get the text to wrap when it is greater than the width of the frame. I simply get the ellipsis (...) at the end of my 'one line' of text.
This wraps when rendering to the screen so am wondering if I'm missing something for writing to pdf and flagging it to wrap. Can anyone offer suggestions?
Well after some research it seems that the LineBreak mode was set to TruncateTail. As the article referenced here uses a .xib file to specify the text etc., I was setting the attributed label text to Word Wrap but only from within the attributed text inspector for that label. From what I can see, this is ignored and I had to set the Line Breaks setting to Word Wrap to get the attribute change.
I am trying to use CoreText on iOS to render OpenGL textures.
CoreText renders in a CoreGraphics Bitmap context, which is then loaded in OpenGL using glTexImage2D.
When I create a bitmap context using an RGB color space evertyhing works fine
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
uint8_t *data = (uint8_t *)calloc(height, 4 * width);
CGContextRef context = CGBitmapContextCreate(data, width, height, 8, 4 * width, colorSpace, kCGImageAlphaNoneSkipLast);
CGColorSpaceRelease(colorSpace);
However, I would like to use a grayscale only color space. When I do the text does not appear.
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
uint8_t *data = (uint8_t *)calloc(height, width);
CGContextRef context = CGBitmapContextCreate(data, width, height, 8, width, colorSpace, kCGImageAlphaNone);
CGColorSpaceRelease(colorSpace);
The text I am rendering is black.
In both cases I can draw in the context using CoreGraphics methods.
I draw the text using the fllowing code:
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)text);
CGSize dimensions = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, [text length]), NULL, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), NULL);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, dimensions.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGRect box = CGRectMake(0, 0, dimensions.width, dimensions.height);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, box );
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [text length]), path, NULL);
CTFrameDraw(frame, context);
CFRelease(frame);
CFRelease(path);
CFRelease(framesetter);
Is there a special setting in CoreText to make this work ?
Thanks
OK, i managed to reproduce the problem. In your NSAttributeString property dictionary, you shouldn't use a UIColor (NSColor?), but a CGColorRef. This way the CGContext APIs will know how to treat your color depending on the colorspace. If you just do the following, you should be ok to go.
CGColorRef col = [UIColor blackColor].CGColor;
NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
//whatever attributes you need
col, kCTForegroundColorAttributeName,
nil];
NSAttributedString *stringToDraw = [[NSAttributedString alloc] initWithString:yourText
attributes:attributesDict];
I hope this helps, let me know if it works!
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.
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 am generating a PDF using the below code but it leads to memory leak can anybody help ?
the code is given below.
- (void)drawText:(NSString*)textToDraw inFrame:(CGRect)frameRect {
NSMutableAttributedString *string = [[[NSMutableAttributedString alloc]
initWithString:textToDraw] autorelease];
// make a few words bold
CTFontRef helveticaBold = CTFontCreateWithName(CFSTR("Helvetica-Bold"), 8.0, NULL);
[string addAttribute:(id)kCTFontAttributeName
value:(id)helveticaBold
range:NSMakeRange(0, [string length])];
// add some color.
if (_flag == 1) {
[string addAttribute:(id)kCTForegroundColorAttributeName
value:(id)[UIColor whiteColor].CGColor
range:NSMakeRange(0, [string length])];
} else {
[string addAttribute:(id)kCTForegroundColorAttributeName
value:(id)[UIColor blackColor].CGColor
range:NSMakeRange(0, [string length])];
}
// layout master
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)string);
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);
CGContextSetRGBFillColor(currentContext, 0, 0, 0, 1.0);
// 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);
}
I'm calling this function several time while generation the
PDF and every time this leads to memory leak.
CTFontCreateWithName follows the create-name-rule, which is if you create it, you own it and you have to release it when you're finished:
CFRelease(helveticaBold);