CTFrame dropping glyphs at the edge - objective-c

I am trying to render Arabic text in my iOS app with custom TTF font (scheherazade) using core-text, which works for the most part - however certain glyphs at the edge of the CTFrame are dropped.
When I adjust the frame-size to make the dropped-glyphs appear in the interior of the frame, they display corretly, which leads me believe something is going wrong in inside CTFrameDraw. Below is the code I'm using to render the Arabic-text:
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, v.textFrame.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGMutablePathRef path = CGPathCreateMutable(); //1
CGPathAddRect(path, NULL, v.textFrame );
CGFloat minLineHeight = 60.0;
CGFloat maxLineHeight = 60.0;
CTTextAlignment paragraphAlignment = kCTRightTextAlignment;
CTLineBreakMode lineBrkMode = kCTLineBreakByWordWrapping;
CTParagraphStyleSetting setting[4] = {
{kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &paragraphAlignment},
{kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &minLineHeight},
{kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &maxLineHeight},
{kCTParagraphStyleSpecifierLineBreakMode, sizeof(CTLineBreakMode), &lineBrkMode}
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(setting, 4);
NSDictionary *attr = [NSDictionary dictionaryWithObjectsAndKeys:
(id)v.arabicFont, (id)kCTFontAttributeName,
paragraphStyle, (id)kCTParagraphStyleAttributeName,
nil];
CFRelease(paragraphStyle);
NSAttributedString* attString = [[[NSAttributedString alloc]
initWithString:v.verseText attributes:attr] autorelease]; //2
CTFramesetterRef framesetter =
CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString); //3
CTFrameRef frame =
CTFramesetterCreateFrame(framesetter,
CFRangeMake(0, [attString length]), path, NULL);
CTFrameDraw(frame, context); //4
CFRelease(frame); //5
CFRelease(path);
CFRelease(framesetter);
Also attached are the screenshots showing the problem I face. Any help would be much appreciated. Thanks.
invalid: http://stellarbeacon.com.au/invalid.png
valid : http://stellarbeacon.com.au/valid.png

There are some bugs in CoreText related to determining the correct frame size. Some of these where fixed in iOS 6, e.g. http://www.cocoanetics.com/2012/02/radar-coretext-line-spacing-bug/
If your problem still exists there then you should file a Radar with the specifics. Also you should open a call with Apple DTS which can probably provide you with a workaround. Often - if your problem is indeed a bug - then you get your DTS call credited back.
PS: try to display your text with my DTCoreText views which do manual layouting and display and see if the problem can be reproduced there. If not then you have your workaround.

Related

iOS 8 API printing PDFs: broken when drawing text?

I have an app that has been happily generating PDFs using quartz/UIKit since iOS 4, but since upgrading the project to iOS 8, crashes whenever it tries to render text into the PDF context. Drawing lines & rectangles is fine, but any permutation of string rendering fails with an exception in one of the low level libraries.
Rather than posting my own source, I tried working backwards from Apple's documentation. Granted it is out of date, but if it's no longer supposed to work, they ought to have fixed it.
https://developer.apple.com/library/ios/documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GeneratingPDF/GeneratingPDF.html
Adapted source code:
- (void)producePDF
{
NSString *text=#"Bzorg blarf gloop foo!";
CFAttributedStringRef currentText = CFAttributedStringCreate(NULL, (CFStringRef)text, NULL);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
NSString *pdfFileName = fullPath;
// Create the PDF context using the default page 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, 612, 792), 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 renderPageWithTextRange:currentRange andFramesetter:framesetter];
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);
// Release the attributed string.
CFRelease(currentText);
}
- (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, 792);
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;
}
I've tried numerous permutations, and they all seem to fail at the exact point of rendering text. The Apple-derived example above dies at the line:
CTFrameDraw(frameRef, currentContext);
Other code attempts to get the minimum working:
NSMutableParagraphStyle* textStyle = NSMutableParagraphStyle.defaultParagraphStyle.mutableCopy;
textStyle.alignment = NSTextAlignmentLeft;
NSDictionary* textFontAttributes = #{
NSFontAttributeName: [UIFont fontWithName: #"Helvetica" size: 12], NSForegroundColorAttributeName: UIColor.redColor,
NSParagraphStyleAttributeName: textStyle};
[#"Hello, World!" drawAtPoint:CGPointZero withAttributes:textFontAttributes];
... crashes at the "drawAtPoint" call.
For what it's worth, if I execute the app on a device without the debugger attached (i.e. run/kill/launch from springboard), the PDF creation works just fine. Presumably whatever bogus exception was getting thrown just gets ignored in real life.

CTFrame clipping first line of text

I am running into a problem while using Core Text, where the first line of the text I display in a CTFrame is cut off at the top, as seen in the screenshot below, with the character "B":
I think I'm doing something wrong while setting the leading in the CTFrame. My code is below:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
NSAttributedString *myString;
//Create the rectangle into which we'll draw the text
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds);
//Flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
//Setup leading (line height)
CGFloat lineHeight = 25;
CTParagraphStyleSetting settings[] = {
{ kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &lineHeight },
{ kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &lineHeight },
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(settings, sizeof(settings) / sizeof(settings[0]));
NSDictionary * attrs = [[NSDictionary alloc] initWithObjectsAndKeys:
(__bridge id)CTFontCreateWithName((__bridge CFStringRef) font.fontName, font.pointSize, NULL) ,
(NSString*)kCTFontAttributeName,
(id)textColor.CGColor,
(NSString*)kCTForegroundColorAttributeName,
(__bridge id) paragraphStyle,
kCTParagraphStyleAttributeName,
nil];
myString = [[NSAttributedString alloc] initWithString:text attributes:attrs];
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)myString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0,[myString length]), path, NULL);
CTFrameDraw(frame, context);
CFRelease(frame);
CFRelease(framesetter);
CFRelease(path);
}
Other SO posts (this one and this one) are not really helpful.
How can I prevent the CTFrame from clipping the first line?
--EDIT--
Reducing lineheight to 20:
lineheight from the second line onwards is respected, but the baseline of the first line of text is less than 20 below the top.
Here CGFloat lineHeight = 25; I am sure (from your screen shot) its less than font size you are currently using (font.pointSize).
This is the only reason of clipping top of text, as text size not able to fit in 25 points of height.
You have two options:
1) Remove paragraphStyle and its related code. Replace your NSDictionary * attrs = … by following code.
NSDictionary * attrs = [[NSDictionary alloc] initWithObjectsAndKeys:
(__bridge id)CTFontCreateWithName((__bridge CFStringRef) font.fontName, font.pointSize, NULL) ,
(NSString*)kCTFontAttributeName,
(id)textColor.CGColor, (NSString*)kCTForegroundColorAttributeName,
nil];
CTFrameDraw() will take care for line height automatically.
2) Or always provide lineHeight greater than your max font size. Like lineHeight = font.pointSize + somemargine;
To understand above explanation try this:
In your current code set your lineHeight = 20;; you can see more text will be clipped from top line and second line text will be more closed to bottom of its top line.
I managed to fix the problem. In the end it turned out to be the location at which I flipped the coordinate system, and by flipping it earlier on in the code the problem fixed itself.

ctframe not updating ctlines

Im using core text to display some json text from the web.
The contents are pulled from a nsdictionary that is popuplated to a UITABLEVIEW.
My problem is that the ctframe doesnt want to update the latest content, it keeps redrawing the old text. Ive debugged it and im pretty sure the ctframesettercreateframe is using the latest NSAttributeString
here is my code,
I call this function at every point to display a new text. The markup parser just returns an NSAtributeString
- (void)assign_text:(NSString*)text{
self.text = text;
CGMutablePathRef path = CGPathCreateMutable(); //1
CGPathAddRect(path, NULL, self.bounds );
MarkupParser *markup = [[MarkupParser alloc]init];
NSAttributedString* attString = [markup attrStringFromMarkup: self.text];
CFAttributedStringRef a = (__bridge_retained CFAttributedStringRef)attString;
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(a); //3
ctFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attString length]), path, NULL);
CFRelease(a);
CFRelease(path);
CFRelease(framesetter);
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CTFrameDraw((CTFrameRef)ctFrame, context);
}
After you modify your ctFrame instance variable, you need to send yourself the setNeedsDisplay message:
ctFrame = CTFramesetterCreateFrame(...);
[self setNeedsDisplay];
Read about the view drawing cycle in the View Programming Guide for iOS.

CATextLayer + NSAttributtedString + CTParagraphStyleRef

I want to have some text with a custom line-spacing, so I wrote an attribute string with CTParagraphStyleAttributte and pass it to my CATextLayer:
UIFont *font = [UIFont systemFontOfSize:20];
CTFontRef ctFont = CTFontCreateWithName((CFStringRef)font.fontName,
font.pointSize, NULL);
CGColorRef cgColor = [UIColor whiteColor].CGColor;
CGFloat leading = 25.0;
CTTextAlignment alignment = kCTRightTextAlignment; // just for test purposes
const CTParagraphStyleSetting styleSettings[] = {
{kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &leading},
{kCTParagraphStyleSpecifierAlignment, sizeof(CTTextAlignment), &alignment}
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(styleSettings, 2));
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
(id)ctFont, (id)kCTFontAttributeName,
(id)cgColor, (id)kCTForegroundColorAttributeName,
(id)paragraphStyle, (id)kCTParagraphStyleAttributeName,
nil];
CFRelease(ctFont);
CFRelease(paragraphStyle);
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc]
initWithString:string
attributes:attributes];
_textLayer.string = attrStr;
[attrStr release];
But the line height is not changing. I think I am missing something here but I don't know what.
I've tried with kCTParagraphStyleSpecifierLineSpacingAdjustment and kCTParagraphStyleSpecifierLineSpacing but either of them don't seem to work (?). I tried also to set the alignment using kCTParagraphStyleSpecifierAlignment (I know CATextLayer has a property for that) just to test kCTParagraphStyleAttributeName is indeed working and it didn't.
I've noticed that even if I pass some crazy values (for example: CTParagraphStyleCreate(styleSettings, -555);) which leads me to ask myself: Does CATextLayer support paragraph attributes? If so, what am I missing here?
I tried your code, putting the NSAttributedString in a CATextLayer, and it ignored the formatting, as you said.
Then I tried drawing the exact same attributed string to a UIView drawRect method using CTFrameDraw, and it obeyed all your formatting. I can only assume that CATextLayer ignores the majority of its formatting. The CATextLayer Class Reference has a number of warnings about what it does in the interests of efficiency.
If you really need to draw to a CALayer, not a UIView, you may be able to create your own CALayer subclass or delegate and do the drawing there.
- (void)drawRect:(CGRect)rect
{
//
// Build attrStr as before.
//
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect bounds = [self bounds];
// Text ends up drawn inverted, so we have to reverse it.
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
CGContextTranslateCTM( ctx, bounds.origin.x, bounds.origin.y+bounds.size.height );
CGContextScaleCTM( ctx, 1, -1 );
// Build a rectangle for drawing in.
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, bounds);
// Create the frame and draw it into the graphics context
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrStr);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
CFRelease(framesetter);
CFRelease(path);
// Finally do the drawing.
CTFrameDraw(frame, ctx);
CFRelease(frame);
}

programmatically create links in a PDF file

While I think it is a basic question, I didn't manage to find a response that works yet. I am creating a PDF file by stroking paths to a PDF context, and I want different areas on the drawing to be hyperlinks to outside contents (http://bla.bla). I'd be happy even with areas that are non-intersecting rectangles. Anyone knows how to do that?
check the answer to this question it works:Embed hyperlink in PDF using Core Graphics on iOS.
- (void) drawTextLink:(NSString *) text inFrame:(CGRect) frameRect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform ctm = CGContextGetCTM(context);
// Translate the origin to the bottom left.
// Notice that 842 is the size of the PDF page.
CGAffineTransformTranslate(ctm, 0.0, 842);
// Flip the handedness of the coordinate system back to right handed.
CGAffineTransformScale(ctm, 1.0, -1.0);
// Convert the update rectangle to the new coordiante system.
CGRect xformRect = CGRectApplyAffineTransform(frameRect, ctm);
NSURL *url = [NSURL URLWithString:text];
UIGraphicsSetPDFContextURLForRect( url, xformRect );
CGContextSaveGState(context);
NSDictionary *attributesDict;
NSMutableAttributedString *attString;
NSNumber *underline = [NSNumber numberWithInt:NSUnderlineStyleSingle];
attributesDict = #{NSUnderlineStyleAttributeName : underline, NSForegroundColorAttributeName : [UIColor blueColor]};
attString = [[NSMutableAttributedString alloc] initWithString:url.absoluteString attributes:attributesDict];
[attString drawInRect:frameRect];
CGContextRestoreGState(context);
}