UILabel default kerning different from CATextLayer
Asked Answered
L

3

12

I have a UILabel with the string 'LA'. I also have a CATextLayer with the same characters in an NSAttributedString assigned to its string property. The kerning in the UILabel is noticeably different from the CATextLayer. Here's the code.

- (void)viewDidLoad
{
    [super viewDidLoad];

    // 
    // UILabel
    //
    UILabel *label1 = [[UILabel alloc] initWithFrame:CGRectMake(20, 50, 280, 100)];
    label1.text = @"LA";
    label1.backgroundColor = [UIColor clearColor];
    label1.font = [UIFont fontWithName:@"Futura" size:90.0];
    [self.view addSubview:label1];

    //
    // CATextLayer
    //
    UILabel *label2 = [[UILabel alloc] initWithFrame:CGRectMake(20, 130, 280, 100)];
    label2.backgroundColor = [UIColor clearColor];
    CATextLayer *textLayer = [[CATextLayer alloc] init];
    textLayer.frame = label2.layer.bounds;
    textLayer.contentsScale = [[UIScreen mainScreen] scale];
    [label2.layer addSublayer:textLayer];
    NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:@"LA"];
    CTFontRef aFont = CTFontCreateWithName((__bridge CFStringRef)@"Futura", 90.0, NULL);
    [string addAttribute:(NSString*)kCTFontAttributeName value:(__bridge id)aFont range:NSMakeRange(0, [string length])];
    textLayer.string = string;
    [self.view addSubview:label2];
}

Here's an image of the results.

Why is the kerning different between these two methods and what am I doing wrong in the CATextLayer example?

Leisured answered 7/5, 2012 at 5:29 Comment(1)
did you find a solution to this question?Rutheruthenia
S
3

UIKit generally uses WebKit for its text rendering (as visible in this crash log), most likely for performance reasons. If you really need super-precision then there are some custom UILabel reimplementations using CoreText as its back-end.

EDIT: As of iOS7 this is no longer true since UILabel uses TextKit for its rendering which is based on CoreText as well.

Strangulation answered 6/10, 2012 at 18:36 Comment(0)
Z
1

you should add attribute to your NSMutableAttributedString.

For the kerning:

    CGFloat characterspacing = 10.0f;
    CFNumberRef num = CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt8Type,&characterspacing);
    [string addAttribute:(id)kCTKernAttributeName value:(id)num range:NSMakeRange(0 , [string length])];
    CFRelease(num);

If you also need the line spacing, or set LineBreadMode:

    CTLineBreakMode linebreak = kCTLineBreakByCharWrapping;
    CTParagraphStyleSetting linebreakStyle;
    linebreakStyle.spec = kCTParagraphStyleSpecifierLineBreakMode;
    linebreakStyle.valueSize = sizeof(linebreak);
    linebreakStyle.value = &linebreak;

    CTParagraphStyleSetting lineSpaceStyle;
    CGFloat linespacing = self.linesSpacing;
    lineSpaceStyle.spec = kCTParagraphStyleSpecifierLineSpacingAdjustment;
    lineSpaceStyle.valueSize = sizeof(linespacing);
    lineSpaceStyle.value =&linespacing;
    CTParagraphStyleSetting settings[ ] ={linebreakStyle,lineSpaceStyle};
    CTParagraphStyleRef style = CTParagraphStyleCreate(settings ,2);
    [string addAttribute:(id)kCTParagraphStyleAttributeName value:(id)style range:NSMakeRange(0 , [string length])];
    CFRelease(style);

At the end, may you need calculate the number of line(linenum) about your kerning,line spacing and LineBreakMode:

CTFramesetterRef myframesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)string);
CGMutablePathRef leftColumnPath = CGPathCreateMutable();
CGPathAddRect(leftColumnPath, NULL ,CGRectMake(0 , 0 , Lable.frame.size.width, MAXFLOAT));
CTFrameRef leftFrame = CTFramesetterCreateFrame(myframesetter,CFRangeMake(0, 0), leftColumnPath , NULL);
CFArrayRef lines = CTFrameGetLines(leftFrame);
linenum = (int)CFArrayGetCount(lines);
CFRelease(myframesetter);
CFRelease(leftFrame);
CGPathRelease(leftColumnPath);
Zellazelle answered 4/3, 2013 at 6:44 Comment(0)
S
0

Well Core Text is really different when compared to drawing strings using UIKit, probably because it comes from Core Foundation and not AppKit or UIKit. I do understand your requirements to use a label for doing the metrics hard job on a string. The only solution for me is to match the kerning of UILabel in the attributed string, unfortunately I don't know the exact value but you can use this property to change that value kCTKernAttributeName. You should pay attention also for the interline that could be not the same.
Forcing that value to the matching kerning you could have the correct behavior. If you want the opposite (match CT kerning) you should do some math an later apply to the label a UIEdgeInset to math the correct label.
Hope this helps.

Saree answered 29/5, 2012 at 5:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.