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.
Related
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);
}
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);
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 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.