Has anyone encountered Xcode failing at UIGraphicsEndPDFContext() after drawing to a context with Japanese text from a NSAttributedString? Luckily the first time I tested my code in Japanese, my iPhone was off-line and it worked. However, much later when I ran the same code in the simulator, it failed and I spent a lot time time trying to debug it. It will also fail when trying to run it from Xcode on an iPhone or iPad when hardwired to the Mac. Seems like trying to run from Xcode in Japanese in this way is the problem. The Mac was set to English.
Following is the method I used for the drawing. It is closely based on the example published by Erica Sadun in her book, "The iOS 5 Developer's Cookbook" which was a good read early on for me. It is placed after a UIGraphicsBeginPDFContextToData() and UIGraphicsBeginPDFPageWithInfo() sequence and before a UIGraphicsEndPDFContext() call which all works fine in English. I don't necessarily need to fix this. I just wanted to alert others so that they might be able to avoid the two days I spent trying to figure this issue out.
- (void)drawText:(NSAttributedString *)textToDraw inFrame:(CGRect)frameRect bkgColor:(UIColor *)backgroundColor {
CFAttributedStringRef currentText = (__bridge CFAttributedStringRef)(textToDraw);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
// Draw background when a color is set
if (backgroundColor) {
[backgroundColor set];
CGRect backgroundFrame = CGRectMake(frameRect.origin.x, frameRect.origin.y -backgroundOffset, frameRect.size.width, frameRect.size.height -3);
//CGRect backgroundFrame = CGRectMake(frameRect.origin.x, frameRect.origin.y -2, frameRect.size.width, frameRect.size.height -3);
CGContextFillRect( UIGraphicsGetCurrentContext(), backgroundFrame);
[[UIColor whiteColor] set];
}
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 so no old scaling factors are left in place.
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
int adjust = frameRect.origin.y * 2 + frameRect.size.height;
// Core Text draws from the bottom-left corner up, so flip
// the current transform prior to drawing.
CGContextTranslateCTM(currentContext, 0, adjust);
CGContextScaleCTM(currentContext, 1.0, -1.0);
// Draw the frame.
CTFrameDraw(frameRef, currentContext);
CGContextScaleCTM(currentContext, 1.0, -1.0);
CGContextTranslateCTM(currentContext, 0, (-1)*adjust);
CFRelease(frameRef);
CFRelease(framesetter);
backgroundColor = nil;
}
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'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);
}
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 using the following code to draw a Core Text text frame which is then added to Quartz 2D drawn shape. The problem is that the text is drawn from bottom to top, instead of top to bottom (see screenshot). Can anyone help ?
- (void)drawText:(CGContextRef)contextP startX:(float)x startY:(float)
y withText:(NSString *)standString
{
CGContextSetTextMatrix(contextP, CGAffineTransformMake(1.0,0.0, 0.0, -1.0, 0.0, 0.0));
CGRect frameText = CGRectMake(0, 0, (right-left)*2, (bottom-top)*2);
NSMutableAttributedString * attrString = [[NSMutableAttributedString alloc] initWithString:#"This is a test string to test the string"];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attrString));
struct CGPath * p = CGPathCreateMutable();
CGPathAddRect(p, NULL, frameText);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0,0), p, NULL);
CTFrameDraw(frame, contextP);
}
In Quartz 2D there are different coordinate system. You have to flip the context, you can use:
CGContextTranslateCTM(currentContext, 0, 100);
CGContextScaleCTM(currentContext, 1.0, -1.0);
It works for me.
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.